From 7bd37cd79d069583f0060321f2529577cf5a899f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 5 Apr 2024 12:16:28 +0200 Subject: [PATCH 01/24] Fix TypeOps::mostSpecific in the case of possible unchecked conversion --- .../pmd/lang/java/types/TypeOps.java | 11 +++++++++-- .../lang/java/types/testdata/LubTestData.java | 3 +++ .../sourceforge/pmd/lang/java/types/GlbTest.kt | 9 +++++++++ .../sourceforge/pmd/lang/java/types/LubTest.kt | 17 +++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java index 54725ad9f8..2b43197800 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java @@ -1733,8 +1733,15 @@ public final class TypeOps { vLoop: for (JTypeMirror v : set) { for (JTypeMirror w : set) { - if (!w.equals(v) && !hasUnresolvedSymbol(w) && isSubtypePure(w, v).bySubtyping()) { - continue vLoop; + if (!w.equals(v) && !hasUnresolvedSymbol(w)) { + Convertibility isConvertible = isSubtypePure(w, v); + if (isConvertible.bySubtyping() + // This last case covers unchecked conversion. It is made antisymmetric by the + // test for a symbol. eg |G| <~> G so it would fail. + // However, |G| ~> S if |G| <: |S|, so we should consider |G| more specific than S. + || isConvertible.somehow() && !Objects.equals(w.getSymbol(), v.getSymbol())) { + continue vLoop; + } } } result.add(v); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java index a6d9a68e3a..a75b21d5e9 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java @@ -46,5 +46,8 @@ public class LubTestData { public static class GenericSub2 extends GenericSuper implements I2, I4 { } + public interface EnumSuperItf {} + public enum Enum1 implements EnumSuperItf {} + public enum Enum2 implements EnumSuperItf {} } diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt index 0e9f8dff3f..9b8d0b33d8 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt @@ -10,6 +10,7 @@ import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import io.kotest.property.checkAll +import io.mockk.InternalPlatformDsl.toArray import net.sourceforge.pmd.lang.test.ast.shouldBeA import net.sourceforge.pmd.lang.java.symbols.internal.asm.createUnresolvedAsmSymbol @@ -71,6 +72,14 @@ class GlbTest : FunSpec({ } + test("Test GLB of arrays") { + + glb(ts.SERIALIZABLE.toArray(), t_ArrayList.toArray()) shouldBe t_ArrayList.toArray() + glb(t_ArrayList.toArray(), ts.SERIALIZABLE.toArray()) shouldBe t_ArrayList.toArray() + glb(t_List.toArray(), `t_List{?}`.toArray()) shouldBe `t_List{?}`.toArray() + + } + test("Test lub of zero types") { shouldThrow { diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/LubTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/LubTest.kt index c863a5079c..153c0e11e5 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/LubTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/LubTest.kt @@ -8,6 +8,7 @@ import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.property.checkAll +import net.sourceforge.pmd.lang.java.types.testdata.LubTestData import net.sourceforge.pmd.lang.java.types.testdata.LubTestData.* /** @@ -140,6 +141,22 @@ class LubTest : FunSpec({ lub(t_List[Sub1::class.decl], t_List[Sub2::class.decl]) shouldBe result } + + test("Test lub of related arrays") { + + // the component type + lub( + Enum1::class.decl, + Enum2::class.decl, + ) shouldBe t_Enum[`?` extends t_Enum[`?`] * EnumSuperItf::class.decl] * EnumSuperItf::class.decl + + // let's try arrays + lub( + Enum1::class.decl.toArray(), + Enum2::class.decl.toArray(), + ) shouldBe t_Enum[`?` extends t_Enum[`?`] * EnumSuperItf::class.decl] * EnumSuperItf::class.decl + + } } } From 277601f9ec62afa4ab748c3cbf5101f1e9e4e15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 5 Apr 2024 13:08:27 +0200 Subject: [PATCH 02/24] Fix lub and glb of arrays We still have a problem with associativity of glb being broken. --- .../sourceforge/pmd/lang/java/types/Lub.java | 42 +++++++++++++------ .../lang/java/types/testdata/LubTestData.java | 3 ++ .../pmd/lang/java/types/GlbTest.kt | 32 ++++++++++++-- .../pmd/lang/java/types/LubTest.kt | 3 +- 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java index 64c4e3918a..49e9e77fb3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java @@ -4,10 +4,11 @@ package net.sourceforge.pmd.lang.java.types; +import static net.sourceforge.pmd.util.CollectionUtil.setOf; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; @@ -69,7 +70,8 @@ final class Lub { * * @return null if G is not a generic type, otherwise Relevant(G) */ - @SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull") // null is explicit mentioned as a possible return value + @SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull") + // null is explicit mentioned as a possible return value static @Nullable List relevant(JClassType g, Set stunion) { if (!g.isRaw()) { return null; @@ -259,7 +261,9 @@ final class Lub { } } - /** Simple record type for a pair of types. */ + /** + * Simple record type for a pair of types. + */ private static final class TypePair { public final JTypeMirror left; @@ -319,34 +323,48 @@ final class Lub { return mostSpecific.iterator().next(); } - List bounds = new ArrayList<>(mostSpecific); + List bounds = new ArrayList<>(mostSpecific.size()); + bounds.add(null); // first element will be replaced with primary bound JTypeMirror primaryBound = null; - for (int i = 0; i < bounds.size(); i++) { - JTypeMirror ci = bounds.get(i); - + for (JTypeMirror ci : mostSpecific) { if (isExclusiveIntersectionBound(ci)) { // either Ci is an array, or Ci is a class, or Ci is a type var (possibly captured) // Ci is not unresolved if (primaryBound == null) { primaryBound = ci; - // move primary bound first - Collections.swap(bounds, 0, i); + } else if (ci.isArray() && primaryBound.isArray()) { + // A[] & B[] = (A & B)[] + // Note that since we're after mostSpecific, we already know + // that A is unrelated to B. Therefore if both B and A are classes, + // then A & B cannot exist and so (A & B)[] similarly does not exist. + + JTypeMirror componentGlb = glb(ts, setOf(((JArrayType) ci).getComponentType(), + ((JArrayType) primaryBound).getComponentType())); + primaryBound = ts.arrayType(componentGlb); } else { throw new IllegalArgumentException( "Bad intersection, unrelated class types " + ci + " and " + primaryBound + " in " + types ); } + } else { + bounds.add(ci); } } + if (primaryBound == null) { - if (bounds.size() == 1) { - return bounds.get(0); - } primaryBound = ts.OBJECT; } + bounds.set(0, primaryBound); // set the primary bound + if (primaryBound == ts.OBJECT) { + // if primary bound is object, it does not appear in the bounds + bounds = bounds.subList(1, bounds.size()); + } + if (bounds.size() == 1) { + return bounds.get(0); // not an intersection + } return new JIntersectionType(ts, primaryBound, bounds); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java index a75b21d5e9..61c4db08b1 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java @@ -50,4 +50,7 @@ public class LubTestData { public enum Enum1 implements EnumSuperItf {} public enum Enum2 implements EnumSuperItf {} + // unrelated + public static class C1 {} + public static class C2 {} } diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt index 9b8d0b33d8..d036571f56 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt @@ -10,9 +10,9 @@ import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import io.kotest.property.checkAll -import io.mockk.InternalPlatformDsl.toArray -import net.sourceforge.pmd.lang.test.ast.shouldBeA import net.sourceforge.pmd.lang.java.symbols.internal.asm.createUnresolvedAsmSymbol +import net.sourceforge.pmd.lang.java.types.testdata.LubTestData +import net.sourceforge.pmd.lang.test.ast.shouldBeA /** * Tests "the greatest lower bound" (glb). @@ -81,6 +81,23 @@ class GlbTest : FunSpec({ } + test("Test GLB of arrays of unrelated type") { + + // C1 & C2 does not exist as they are both unrelated classes + shouldThrow { + glb(LubTestData.C1::class.decl, LubTestData.C2::class.decl) + } + + // C1[] & C2[] = (C1 & C2)[] equally does not exist + shouldThrow { + glb(LubTestData.C1::class.decl.toArray(), LubTestData.C2::class.decl.toArray()) + } + + // but C1[] & I1[] = (C1 & I1)[] exists because I1 is an interface. + glb(LubTestData.C1::class.decl.toArray(), LubTestData.I1::class.decl.toArray()) + } + + test("Test lub of zero types") { shouldThrow { ts.glb(emptyList()) @@ -118,8 +135,17 @@ class GlbTest : FunSpec({ } glb(`t_List{? extends Number}`, `t_Collection{Integer}`, `t_ArrayList{Integer}`) shouldBe `t_ArrayList{Integer}` glb(`t_List{? extends Number}`, `t_List{String}`, `t_Enum{JPrimitiveType}`).shouldBeA { - it.components.shouldContainExactly(`t_Enum{JPrimitiveType}`, `t_List{String}`, `t_List{? extends Number}`) + it.components.shouldContainExactly(`t_Enum{JPrimitiveType}`, `t_List{? extends Number}`, `t_List{String}`) } + + + glb(t_Collection[`?` extends t_Number], + `t_List{?}`, + t_List) shouldBe `t_List{?}` // todo check that + + glb(t_List, `t_List{?}`) shouldBe `t_List{?}` + glb(t_Collection[`?` extends t_Number], `t_List{?}`) shouldBe `t_List{?}` + } test("Test GLB with unresolved things") { diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/LubTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/LubTest.kt index 153c0e11e5..c896047adb 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/LubTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/LubTest.kt @@ -8,7 +8,6 @@ import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.property.checkAll -import net.sourceforge.pmd.lang.java.types.testdata.LubTestData import net.sourceforge.pmd.lang.java.types.testdata.LubTestData.* /** @@ -154,7 +153,7 @@ class LubTest : FunSpec({ lub( Enum1::class.decl.toArray(), Enum2::class.decl.toArray(), - ) shouldBe t_Enum[`?` extends t_Enum[`?`] * EnumSuperItf::class.decl] * EnumSuperItf::class.decl + ) shouldBe (t_Enum * EnumSuperItf::class.decl).toArray() } } From 6f1f72de1e70a4eba61741ed83aa64f74ea6877c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 5 Apr 2024 15:59:12 +0200 Subject: [PATCH 03/24] Doc --- .../sourceforge/pmd/lang/java/types/TypeSystem.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeSystem.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeSystem.java index c256bfc690..57d77848d3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeSystem.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeSystem.java @@ -697,12 +697,20 @@ public final class TypeSystem { *
  • The intersection has a single component that is a * class, array, or type variable. If all components are interfaces, * then that component is {@link #OBJECT}. + *
  • If several components are arrays, then their components + * are intersected: {@code A[] & B[] = (A & B)[]} * * *

    If after these transformations, only a single component remains, * then that is the returned type. Otherwise a {@link JIntersectionType} * is created. Note that the intersection may be unsatisfiable (eg {@code A[] & Runnable}), - * but we don't attempt to minimize this to {@link #NULL_TYPE}. + * but we don't attempt to minimize this to {@link #NULL_TYPE}. Similarly, + * we do not attempt to minimize valid intersections. For instance {@code List & Collection} + * can technically be minimized to {@code List}, but doing this + * requires inference of a fitting parameterization in general, which is + * complex, and not necessary in the internal tasks where intersection types are + * useful. In fact intersection types are precisely useful because they are + * simple to build. * *

    See also JLS§4.9 (Intersection types). * From 89f7aecab830a27810e444781218cda9881b0e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 5 Apr 2024 16:18:08 +0200 Subject: [PATCH 04/24] Fix mostSpecific --- .../pmd/lang/java/types/TypeOps.java | 7 +++- .../pmd/lang/java/types/GlbTest.kt | 32 ++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java index 2b43197800..56c563ddf0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/TypeOps.java @@ -580,6 +580,11 @@ public final class TypeOps { return this == UNCHECKED_WARNING; } + /** True if this is {@link #SUBTYPING} or {@link #UNCHECKED_NO_WARNING}. */ + public boolean withoutWarnings() { + return this == SUBTYPING || this == UNCHECKED_NO_WARNING; + } + // package: @@ -1739,7 +1744,7 @@ public final class TypeOps { // This last case covers unchecked conversion. It is made antisymmetric by the // test for a symbol. eg |G| <~> G so it would fail. // However, |G| ~> S if |G| <: |S|, so we should consider |G| more specific than S. - || isConvertible.somehow() && !Objects.equals(w.getSymbol(), v.getSymbol())) { + || isConvertible.withoutWarnings() && !Objects.equals(w.getSymbol(), v.getSymbol())) { continue vLoop; } } diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt index d036571f56..4f4da8bba9 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt @@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang.java.types import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import io.kotest.property.checkAll @@ -115,8 +116,23 @@ class GlbTest : FunSpec({ } } + test("test corner case") { + + TypeOps.mostSpecific( + setOf( + t_Collection[`?` extends t_Number], + `t_List{?}`, + t_List + ) + ) shouldBe setOf(t_Collection[`?` extends t_Number], `t_List{?}`) + } + test("Test GLB corner cases") { + // note: intersections are not minimized, or reduced to the null type if they are unsatisfiable. + // note also that in this test we test the components explicitly instead of using the DSL, + // because the DSL operator to create intersections actually calls GLB. + glb(t_Iterable[`?` extends t_Number], t_Iterable[t_String]).shouldBeA { it.components.shouldContainExactly(t_Iterable[`?` extends t_Number], t_Iterable[t_String]) } @@ -138,13 +154,21 @@ class GlbTest : FunSpec({ it.components.shouldContainExactly(`t_Enum{JPrimitiveType}`, `t_List{? extends Number}`, `t_List{String}`) } - - glb(t_Collection[`?` extends t_Number], + glb( + t_Collection[`?` extends t_Number], `t_List{?}`, - t_List) shouldBe `t_List{?}` // todo check that + t_List + ).shouldBeA { + it.components.shouldContainExactlyInAnyOrder(t_Collection[`?` extends t_Number], `t_List{?}`) + } glb(t_List, `t_List{?}`) shouldBe `t_List{?}` - glb(t_Collection[`?` extends t_Number], `t_List{?}`) shouldBe `t_List{?}` + glb( + t_Collection[`?` extends t_Number], + `t_List{?}` + ).shouldBeA { + it.components.shouldContainExactlyInAnyOrder(t_Collection[`?` extends t_Number], `t_List{?}`) + } } From bfe4013883afb0e4257fb542416b5a9f512803ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 5 Apr 2024 16:22:26 +0200 Subject: [PATCH 05/24] Add test case for #4902 --- .../types/internal/infer/TypeInferenceTest.kt | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceTest.kt index d7965cd569..137ffe49ac 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceTest.kt @@ -7,13 +7,13 @@ package net.sourceforge.pmd.lang.java.types.internal.infer import io.kotest.matchers.shouldBe +import net.sourceforge.pmd.lang.java.ast.* +import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol +import net.sourceforge.pmd.lang.java.types.* import net.sourceforge.pmd.lang.test.ast.NodeSpec import net.sourceforge.pmd.lang.test.ast.shouldBe import net.sourceforge.pmd.lang.test.ast.shouldBeA import net.sourceforge.pmd.lang.test.ast.shouldMatchN -import net.sourceforge.pmd.lang.java.ast.* -import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol -import net.sourceforge.pmd.lang.java.types.* import java.util.* /** @@ -384,4 +384,37 @@ class Foo { } + parserTest("#4902 bad intersection") { + + val (acu, spy) = parser.parseWithTypeInferenceSpy(""" +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class BadIntersection { + + interface Animal { } + enum Bird implements Animal { PARROT, CHICKEN } + enum Fish implements Animal { GOLDFISH, MACKEREL } + + private static List combineAnimals() { + return Stream.of( + Bird.values(), + Fish.values() + ) + .flatMap(Arrays::stream) + .collect(Collectors.toList()); + } +} + """.trimIndent()) + + val (_, t_Animal) = acu.declaredTypeSignatures() + + spy.shouldBeOk { + acu.firstMethodCall() shouldHaveType java.util.List::class[t_Animal] + } + } + + }) From 4a25afc86459cc788905d930173a735e94352e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 5 Apr 2024 16:24:11 +0200 Subject: [PATCH 06/24] Checkstyle --- .../lang/java/types/testdata/LubTestData.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java index 61c4db08b1..ba951fefbd 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/testdata/LubTestData.java @@ -46,11 +46,19 @@ public class LubTestData { public static class GenericSub2 extends GenericSuper implements I2, I4 { } - public interface EnumSuperItf {} - public enum Enum1 implements EnumSuperItf {} - public enum Enum2 implements EnumSuperItf {} + public interface EnumSuperItf { + } + + public enum Enum1 implements EnumSuperItf { + } + + public enum Enum2 implements EnumSuperItf { + } // unrelated - public static class C1 {} - public static class C2 {} + public static class C1 { + } + + public static class C2 { + } } From 87691a0321f25afde403f00a6052c88586a03291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 5 Apr 2024 16:32:21 +0200 Subject: [PATCH 07/24] Self review --- .../java/net/sourceforge/pmd/lang/java/types/Lub.java | 7 ++----- .../net/sourceforge/pmd/lang/java/types/GlbTest.kt | 9 +++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java index 49e9e77fb3..80e6ac5575 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/Lub.java @@ -70,8 +70,7 @@ final class Lub { * * @return null if G is not a generic type, otherwise Relevant(G) */ - @SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull") - // null is explicit mentioned as a possible return value + @SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull") // null is explicit mentioned as a possible return value static @Nullable List relevant(JClassType g, Set stunion) { if (!g.isRaw()) { return null; @@ -261,9 +260,7 @@ final class Lub { } } - /** - * Simple record type for a pair of types. - */ + /** Simple record type for a pair of types. */ private static final class TypePair { public final JTypeMirror left; diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt index 4f4da8bba9..d244649863 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/GlbTest.kt @@ -96,6 +96,7 @@ class GlbTest : FunSpec({ // but C1[] & I1[] = (C1 & I1)[] exists because I1 is an interface. glb(LubTestData.C1::class.decl.toArray(), LubTestData.I1::class.decl.toArray()) + .shouldBe((LubTestData.C1::class.decl * LubTestData.I1::class.decl).toArray()) } @@ -138,20 +139,20 @@ class GlbTest : FunSpec({ } glb(`t_ArrayList{Integer}`, ts.NULL_TYPE) shouldBe ts.NULL_TYPE glb(`t_ArrayList{Integer}`, t_Iterable[`?` extends t_Number], t_Iterable[t_String]).shouldBeA { - it.components.shouldContainExactly(`t_ArrayList{Integer}`, t_Iterable[t_String]) + it.components.shouldContainExactlyInAnyOrder(`t_ArrayList{Integer}`, t_Iterable[t_String]) } glb(`t_List{? extends Number}`, `t_Collection{Integer}`).shouldBeA { - it.components.shouldContainExactly(`t_List{? extends Number}`, `t_Collection{Integer}`) + it.components.shouldContainExactlyInAnyOrder(`t_List{? extends Number}`, `t_Collection{Integer}`) } glb(t_List.toArray(), t_Iterable).shouldBeA { - it.components.shouldContainExactly(t_List.toArray(), t_Iterable) + it.components.shouldContainExactlyInAnyOrder(t_List.toArray(), t_Iterable) it.inducedClassType.shouldBeNull() } glb(`t_List{? extends Number}`, `t_Collection{Integer}`, `t_ArrayList{Integer}`) shouldBe `t_ArrayList{Integer}` glb(`t_List{? extends Number}`, `t_List{String}`, `t_Enum{JPrimitiveType}`).shouldBeA { - it.components.shouldContainExactly(`t_Enum{JPrimitiveType}`, `t_List{? extends Number}`, `t_List{String}`) + it.components.shouldContainExactlyInAnyOrder(`t_Enum{JPrimitiveType}`, `t_List{String}`, `t_List{? extends Number}`) } glb( From 3203a80f27613d926356b24aa77270ef6b85e318 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Sat, 6 Apr 2024 11:16:06 +0200 Subject: [PATCH 08/24] [apex] Add ASTAnnotation#getRawName Fixes #4418 --- docs/pages/release_notes.md | 2 ++ .../pmd/lang/apex/ast/ASTAnnotation.java | 27 +++++++++++++---- .../pmd/lang/apex/ast/ASTAnnotationTest.java | 30 +++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotationTest.java diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index cdc04217b8..6f948e2a30 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -26,6 +26,8 @@ This is a {{ site.pmd.release_type }} release. * cli * [#4791](https://github.com/pmd/pmd/issues/4791): \[cli] Could not find or load main class * [#4913](https://github.com/pmd/pmd/issues/4913): \[cli] cpd-gui closes immediately +* apex + * [#4418](https://github.com/pmd/pmd/issues/4418): \[apex] ASTAnnotation.getImage() does not return value as written in the class * apex-errorprone * [#3953](https://github.com/pmd/pmd/issues/3953): \[apex] EmptyCatchBlock false positive with formal (doc) comments * java-bestpractices diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java index 02ebf7b0ab..1828d56495 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java @@ -13,8 +13,8 @@ public final class ASTAnnotation extends AbstractApexNode.Single - * Includes all annotations from the official + * + *

    Includes all annotations from the official * documentation, plus *

      *
    • {@code AllowCertifiedApex}
    • @@ -71,21 +71,38 @@ public final class ASTAnnotation extends AbstractApexNode.Single parsed = parse("public with sharing class Example {\n" + + "\n" + + " @istest\n" + + " private static void fooShouldBar() {\n" + + " }\n" + + " \n" + + "}"); + ASTAnnotation annotation = parsed.descendants(ASTAnnotation.class).first(); + + assertEquals("IsTest", annotation.getName()); + assertEquals("istest", annotation.getRawName()); + Chars text = annotation.getTextDocument().sliceOriginalText(annotation.getTextRegion()); + assertEquals("@istest", text.toString()); + } +} From aaed4fb90a4fef685af821196944e09e5a50cf06 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Sat, 6 Apr 2024 12:00:38 +0200 Subject: [PATCH 09/24] [java] Add test case for #1780 --- .../pmd/lang/java/ast/ParserCornersTest.java | 5 ++ .../java/ast/GitHubBug1780OuterClass.java | 21 ++++++ .../lang/java/ast/GitHubBug1780OuterClass.txt | 68 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/GitHubBug1780OuterClass.java create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/GitHubBug1780OuterClass.txt diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java index 826c2b4a74..a6713bfd44 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java @@ -357,4 +357,9 @@ class ParserCornersTest extends BaseJavaTreeDumpTest { void testGitHubBug3642() { doTest("GitHubBug3642"); } + + @Test + void testGitHubBug1780() { + doTest("GitHubBug1780OuterClass"); + } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/GitHubBug1780OuterClass.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/GitHubBug1780OuterClass.java new file mode 100644 index 0000000000..a772a5930b --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/GitHubBug1780OuterClass.java @@ -0,0 +1,21 @@ +package com.pmd.test; + +public class GitHubBug1780OuterClass { + public GitHubBug1780OuterClass() { + System.out.println("Inner Class AdapterClass"); + } + public class InnerClass { + public InnerClass() { + System.out.println("Inner Class Constructor"); + } + } + private static class StaticInnerClass extends InnerClass { + public StaticInnerClass() { + new GitHubBug1780OuterClass().super(); + System.out.println("StaticInnerClass Constructor"); + } + } + public static void main(String args[]) { + new GitHubBug1780OuterClass.StaticInnerClass(); + } +} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/GitHubBug1780OuterClass.txt b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/GitHubBug1780OuterClass.txt new file mode 100644 index 0000000000..aff30eada1 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/GitHubBug1780OuterClass.txt @@ -0,0 +1,68 @@ ++- CompilationUnit[@PackageName = "com.pmd.test"] + +- PackageDeclaration[@Name = "com.pmd.test"] + | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] + +- ClassDeclaration[@Abstract = false, @Annotation = false, @Anonymous = false, @BinaryName = "com.pmd.test.GitHubBug1780OuterClass", @CanonicalName = "com.pmd.test.GitHubBug1780OuterClass", @EffectiveVisibility = Visibility.V_PUBLIC, @Enum = false, @Final = false, @Interface = false, @Local = false, @Nested = false, @PackageName = "com.pmd.test", @Record = false, @RegularClass = true, @RegularInterface = false, @SimpleName = "GitHubBug1780OuterClass", @Static = false, @TopLevel = true, @Visibility = Visibility.V_PUBLIC] + +- ModifierList[@EffectiveModifiers = "{public}", @ExplicitModifiers = "{public}"] + +- ClassBody[@Empty = false, @Size = 4] + +- ConstructorDeclaration[@Abstract = false, @Arity = 0, @EffectiveVisibility = Visibility.V_PUBLIC, @Final = false, @Image = "GitHubBug1780OuterClass", @Name = "GitHubBug1780OuterClass", @Static = false, @Varargs = false, @Visibility = Visibility.V_PUBLIC, @containsComment = false] + | +- ModifierList[@EffectiveModifiers = "{public}", @ExplicitModifiers = "{public}"] + | +- FormalParameters[@Empty = true, @Size = 0] + | +- Block[@Empty = false, @Size = 1, @containsComment = false] + | +- ExpressionStatement[] + | +- MethodCall[@CompileTimeConstant = false, @Image = "println", @MethodName = "println", @ParenthesisDepth = 0, @Parenthesized = false] + | +- FieldAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "out", @Name = "out", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- TypeExpression[@CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false] + | | +- ClassType[@FullyQualified = false, @SimpleName = "System"] + | +- ArgumentList[@Empty = false, @Size = 1] + | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "Inner Class AdapterClass", @Empty = false, @Image = "\"Inner Class AdapterClass\"", @Length = 24, @LiteralText = "\"Inner Class AdapterClass\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] + +- ClassDeclaration[@Abstract = false, @Annotation = false, @Anonymous = false, @BinaryName = "com.pmd.test.GitHubBug1780OuterClass$InnerClass", @CanonicalName = "com.pmd.test.GitHubBug1780OuterClass.InnerClass", @EffectiveVisibility = Visibility.V_PUBLIC, @Enum = false, @Final = false, @Interface = false, @Local = false, @Nested = true, @PackageName = "com.pmd.test", @Record = false, @RegularClass = true, @RegularInterface = false, @SimpleName = "InnerClass", @Static = false, @TopLevel = false, @Visibility = Visibility.V_PUBLIC] + | +- ModifierList[@EffectiveModifiers = "{public}", @ExplicitModifiers = "{public}"] + | +- ClassBody[@Empty = false, @Size = 1] + | +- ConstructorDeclaration[@Abstract = false, @Arity = 0, @EffectiveVisibility = Visibility.V_PUBLIC, @Final = false, @Image = "InnerClass", @Name = "InnerClass", @Static = false, @Varargs = false, @Visibility = Visibility.V_PUBLIC, @containsComment = false] + | +- ModifierList[@EffectiveModifiers = "{public}", @ExplicitModifiers = "{public}"] + | +- FormalParameters[@Empty = true, @Size = 0] + | +- Block[@Empty = false, @Size = 1, @containsComment = false] + | +- ExpressionStatement[] + | +- MethodCall[@CompileTimeConstant = false, @Image = "println", @MethodName = "println", @ParenthesisDepth = 0, @Parenthesized = false] + | +- FieldAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "out", @Name = "out", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- TypeExpression[@CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false] + | | +- ClassType[@FullyQualified = false, @SimpleName = "System"] + | +- ArgumentList[@Empty = false, @Size = 1] + | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "Inner Class Constructor", @Empty = false, @Image = "\"Inner Class Constructor\"", @Length = 23, @LiteralText = "\"Inner Class Constructor\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] + +- ClassDeclaration[@Abstract = false, @Annotation = false, @Anonymous = false, @BinaryName = "com.pmd.test.GitHubBug1780OuterClass$StaticInnerClass", @CanonicalName = "com.pmd.test.GitHubBug1780OuterClass.StaticInnerClass", @EffectiveVisibility = Visibility.V_PRIVATE, @Enum = false, @Final = false, @Interface = false, @Local = false, @Nested = true, @PackageName = "com.pmd.test", @Record = false, @RegularClass = true, @RegularInterface = false, @SimpleName = "StaticInnerClass", @Static = true, @TopLevel = false, @Visibility = Visibility.V_PRIVATE] + | +- ModifierList[@EffectiveModifiers = "{private, static}", @ExplicitModifiers = "{private, static}"] + | +- ExtendsList[@Empty = false, @Size = 1] + | | +- ClassType[@FullyQualified = false, @SimpleName = "InnerClass"] + | +- ClassBody[@Empty = false, @Size = 1] + | +- ConstructorDeclaration[@Abstract = false, @Arity = 0, @EffectiveVisibility = Visibility.V_PRIVATE, @Final = false, @Image = "StaticInnerClass", @Name = "StaticInnerClass", @Static = false, @Varargs = false, @Visibility = Visibility.V_PUBLIC, @containsComment = false] + | +- ModifierList[@EffectiveModifiers = "{public}", @ExplicitModifiers = "{public}"] + | +- FormalParameters[@Empty = true, @Size = 0] + | +- Block[@Empty = false, @Size = 2, @containsComment = false] + | +- ExplicitConstructorInvocation[@ArgumentCount = 0, @MethodName = "new", @Qualified = true, @Super = true, @This = false] + | | +- ConstructorCall[@AnonymousClass = false, @CompileTimeConstant = false, @DiamondTypeArgs = false, @MethodName = "new", @ParenthesisDepth = 0, @Parenthesized = false, @QualifiedInstanceCreation = false] + | | | +- ClassType[@FullyQualified = false, @SimpleName = "GitHubBug1780OuterClass"] + | | | +- ArgumentList[@Empty = true, @Size = 0] + | | +- ArgumentList[@Empty = true, @Size = 0] + | +- ExpressionStatement[] + | +- MethodCall[@CompileTimeConstant = false, @Image = "println", @MethodName = "println", @ParenthesisDepth = 0, @Parenthesized = false] + | +- FieldAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "out", @Name = "out", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- TypeExpression[@CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false] + | | +- ClassType[@FullyQualified = false, @SimpleName = "System"] + | +- ArgumentList[@Empty = false, @Size = 1] + | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "StaticInnerClass Constructor", @Empty = false, @Image = "\"StaticInnerClass Constructor\"", @Length = 28, @LiteralText = "\"StaticInnerClass Constructor\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] + +- MethodDeclaration[@Abstract = false, @Arity = 1, @EffectiveVisibility = Visibility.V_PUBLIC, @Final = false, @MainMethod = true, @Name = "main", @Overridden = false, @Static = true, @Varargs = false, @Visibility = Visibility.V_PUBLIC, @Void = true] + +- ModifierList[@EffectiveModifiers = "{public, static}", @ExplicitModifiers = "{public, static}"] + +- VoidType[] + +- FormalParameters[@Empty = false, @Size = 1] + | +- FormalParameter[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @Varargs = false, @Visibility = Visibility.V_LOCAL] + | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] + | +- ClassType[@FullyQualified = false, @SimpleName = "String"] + | +- VariableId[@ArrayType = true, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = true, @LambdaParameter = false, @LocalVariable = false, @Name = "args", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] + | +- ArrayDimensions[@Empty = false, @Size = 1] + | +- ArrayTypeDim[@Varargs = false] + +- Block[@Empty = false, @Size = 1, @containsComment = false] + +- ExpressionStatement[] + +- ConstructorCall[@AnonymousClass = false, @CompileTimeConstant = false, @DiamondTypeArgs = false, @MethodName = "new", @ParenthesisDepth = 0, @Parenthesized = false, @QualifiedInstanceCreation = false] + +- ClassType[@FullyQualified = false, @SimpleName = "StaticInnerClass"] + | +- ClassType[@FullyQualified = false, @SimpleName = "GitHubBug1780OuterClass"] + +- ArgumentList[@Empty = true, @Size = 0] From 78c43ed87a232e2305891a0c795724708867c7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 6 Apr 2024 16:56:25 +0200 Subject: [PATCH 10/24] [java] Fix #4779 - Improve doc of MethodArgumentCanBeFinal --- .../resources/category/java/codestyle.xml | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pmd-java/src/main/resources/category/java/codestyle.xml b/pmd-java/src/main/resources/category/java/codestyle.xml index 01b868afa7..867b911ed8 100644 --- a/pmd-java/src/main/resources/category/java/codestyle.xml +++ b/pmd-java/src/main/resources/category/java/codestyle.xml @@ -1034,17 +1034,27 @@ public class MissingTheProperSuffix implements SessionBean {} // non-standard class="net.sourceforge.pmd.lang.java.rule.codestyle.MethodArgumentCouldBeFinalRule" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_codestyle.html#methodargumentcouldbefinal"> -A method argument that is never re-assigned within the method can be declared final. + Reports method and constructor parameters that can be made final because they are never reassigned within the body of the method. + + This rule ignores unused parameters so as not to overlap with the rule {% rule 'java/bestpractices/UnusedVariable' %}. + It will also ignore the parameters of abstract methods. 3 From 2a12cb1ef7fe1ff5958d027eb2aa41c71e4ea6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 6 Apr 2024 17:00:27 +0200 Subject: [PATCH 11/24] Fix rule ref --- pmd-java/src/main/resources/category/java/codestyle.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-java/src/main/resources/category/java/codestyle.xml b/pmd-java/src/main/resources/category/java/codestyle.xml index 867b911ed8..4600826fcc 100644 --- a/pmd-java/src/main/resources/category/java/codestyle.xml +++ b/pmd-java/src/main/resources/category/java/codestyle.xml @@ -1036,7 +1036,7 @@ public class MissingTheProperSuffix implements SessionBean {} // non-standard Reports method and constructor parameters that can be made final because they are never reassigned within the body of the method. - This rule ignores unused parameters so as not to overlap with the rule {% rule 'java/bestpractices/UnusedVariable' %}. + This rule ignores unused parameters so as not to overlap with the rule {% rule 'java/bestpractices/UnusedFormalParameter' %}. It will also ignore the parameters of abstract methods. 3 From c81be9d17e8f2d0a57ca8b23fffccf4d80e4a102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sun, 7 Apr 2024 01:49:51 -0300 Subject: [PATCH 12/24] Update changelog, refs #4902 --- docs/pages/release_notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 6f948e2a30..917f886cbe 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -30,6 +30,8 @@ This is a {{ site.pmd.release_type }} release. * [#4418](https://github.com/pmd/pmd/issues/4418): \[apex] ASTAnnotation.getImage() does not return value as written in the class * apex-errorprone * [#3953](https://github.com/pmd/pmd/issues/3953): \[apex] EmptyCatchBlock false positive with formal (doc) comments +* java + * [#4902](https://github.com/pmd/pmd/issues/4902): \[java] "Bad intersection, unrelated class types" for Constable\[] and Enum\[] * java-bestpractices * [#1084](https://github.com/pmd/pmd/issues/1084): \[java] Allow JUnitTestsShouldIncludeAssert to configure verification methods * [#4435](https://github.com/pmd/pmd/issues/4435): \[java] \[7.0-rc1] UnusedAssignment for used field From 2c26e43ae99e36a0676696f94e31c4e5169c3394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Sun, 7 Apr 2024 01:51:41 -0300 Subject: [PATCH 13/24] Update changelog, refs #4779 --- docs/pages/release_notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index cdc04217b8..824ac33b46 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -36,6 +36,7 @@ This is a {{ site.pmd.release_type }} release. * java-codestyle * [#4602](https://github.com/pmd/pmd/issues/4602): \[java] UnnecessaryImport: false positives with static imports * [#4785](https://github.com/pmd/pmd/issues/4785): \[java] False Positive: PMD Incorrectly report violation for UnnecessaryImport + * [#4779](https://github.com/pmd/pmd/issues/4779): \[java] Examples in documentation of MethodArgumentCanBeFinal do not trigger the rule * [#4881](https://github.com/pmd/pmd/issues/4881): \[java] ClassNamingConventions: interfaces are identified as abstract classes (regression in 7.0.0) * java-design * [#3694](https://github.com/pmd/pmd/issues/3694): \[java] SingularField ignores static variables From 32b70b1d89deee49cdd343fe07f635f20bd55294 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 11 Apr 2024 14:48:17 +0200 Subject: [PATCH 14/24] [doc] Fix rule reference --- pmd-java/src/main/resources/category/java/codestyle.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-java/src/main/resources/category/java/codestyle.xml b/pmd-java/src/main/resources/category/java/codestyle.xml index 4600826fcc..6e41c32f32 100644 --- a/pmd-java/src/main/resources/category/java/codestyle.xml +++ b/pmd-java/src/main/resources/category/java/codestyle.xml @@ -1036,7 +1036,7 @@ public class MissingTheProperSuffix implements SessionBean {} // non-standard Reports method and constructor parameters that can be made final because they are never reassigned within the body of the method. - This rule ignores unused parameters so as not to overlap with the rule {% rule 'java/bestpractices/UnusedFormalParameter' %}. + This rule ignores unused parameters so as not to overlap with the rule {% rule java/bestpractices/UnusedFormalParameter %}. It will also ignore the parameters of abstract methods. 3 From 6d9c49858f82c937ee96f465a8ad40023384b25c Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 11 Apr 2024 14:49:10 +0200 Subject: [PATCH 15/24] [ci] Generate rule doc also for pull requests --- .ci/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/build.sh b/.ci/build.sh index 7c11668187..291a10b424 100755 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -36,7 +36,7 @@ function build() { if pmd_ci_utils_is_fork_or_pull_request; then pmd_ci_log_group_start "Build with mvnw" - ./mvnw clean install --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}" + ./mvnw clean install --show-version --errors --batch-mode -Pgenerate-rule-docs "${PMD_MAVEN_EXTRA_OPTS[@]}" pmd_ci_log_group_end # Execute danger and dogfood only for pull requests in our own repository From 62f215929c5b368ec00d3ffb2663af1c8b5f7d2e Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 11 Apr 2024 13:11:54 +0200 Subject: [PATCH 16/24] [core] Disable Caching in URLConnection for ClasspathClassLoader Fixes #4899 --- docs/pages/release_notes.md | 1 + .../internal/util/ClasspathClassLoader.java | 10 ++ .../util/ClasspathClassLoaderTest.java | 105 ++++++++++++++++-- .../java/symbols/internal/asm/ClassStub.java | 3 + .../java/symbols/internal/asm/Loader.java | 2 +- 5 files changed, 112 insertions(+), 9 deletions(-) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 386305f89f..912c4ce1c5 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -31,6 +31,7 @@ This is a {{ site.pmd.release_type }} release. * apex-errorprone * [#3953](https://github.com/pmd/pmd/issues/3953): \[apex] EmptyCatchBlock false positive with formal (doc) comments * java + * [#4899](https://github.com/pmd/pmd/issues/4899): \[java] Parsing failed in ParseLock#doParse() java.io.IOException: Stream closed * [#4902](https://github.com/pmd/pmd/issues/4902): \[java] "Bad intersection, unrelated class types" for Constable\[] and Enum\[] * java-bestpractices * [#1084](https://github.com/pmd/pmd/issues/1084): \[java] Allow JUnitTestsShouldIncludeAssert to configure verification methods diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/ClasspathClassLoader.java b/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/ClasspathClassLoader.java index 2d5da86589..3a73f98a23 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/ClasspathClassLoader.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/ClasspathClassLoader.java @@ -53,6 +53,16 @@ public class ClasspathClassLoader extends URLClassLoader { static { registerAsParallelCapable(); + + // Disable caching for jar files to prevent issues like #4899 + try { + // Uses a pseudo URL to be able to call URLConnection#setDefaultUseCaches + // with Java9+ there is a static method for that per protocol: + // https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/net/URLConnection.html#setDefaultUseCaches(java.lang.String,boolean) + URI.create("jar:file:file.jar!/").toURL().openConnection().setDefaultUseCaches(false); + } catch (IOException e) { + throw new RuntimeException(e); + } } public ClasspathClassLoader(List files, ClassLoader parent) throws IOException { diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/internal/util/ClasspathClassLoaderTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/internal/util/ClasspathClassLoaderTest.java index 50103b9091..d4661fd276 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/internal/util/ClasspathClassLoaderTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/internal/util/ClasspathClassLoaderTest.java @@ -17,6 +17,11 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Semaphore; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -60,27 +65,111 @@ class ClasspathClassLoaderTest { } } - @Test - void loadFromJar() throws IOException { - final String RESOURCE_NAME = "net/sourceforge/pmd/Sample.txt"; - final String TEST_CONTENT = "Test\n"; + private static final String CUSTOM_JAR_RESOURCE = "net/sourceforge/pmd/Sample.txt"; + private static final String CUSTOM_JAR_RESOURCE2 = "net/sourceforge/pmd/Sample2.txt"; + private static final String CUSTOM_JAR_RESOURCE_CONTENT = "Test\n"; + private Path prepareCustomJar() throws IOException { Path jarPath = tempDir.resolve("custom.jar"); try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(jarPath))) { - out.putNextEntry(new ZipEntry(RESOURCE_NAME)); - out.write(TEST_CONTENT.getBytes(StandardCharsets.UTF_8)); + out.putNextEntry(new ZipEntry(CUSTOM_JAR_RESOURCE)); + out.write(CUSTOM_JAR_RESOURCE_CONTENT.getBytes(StandardCharsets.UTF_8)); + out.putNextEntry(new ZipEntry(CUSTOM_JAR_RESOURCE2)); + out.write(CUSTOM_JAR_RESOURCE_CONTENT.getBytes(StandardCharsets.UTF_8)); } + return jarPath; + } + + @Test + void loadFromJar() throws IOException { + Path jarPath = prepareCustomJar(); String classpath = jarPath.toString(); try (ClasspathClassLoader loader = new ClasspathClassLoader(classpath, null)) { - try (InputStream in = loader.getResourceAsStream(RESOURCE_NAME)) { + try (InputStream in = loader.getResourceAsStream(CUSTOM_JAR_RESOURCE)) { assertNotNull(in); String s = IOUtil.readToString(in, StandardCharsets.UTF_8); - assertEquals(TEST_CONTENT, s); + assertEquals(CUSTOM_JAR_RESOURCE_CONTENT, s); } } } + /** + * @see [java] Parsing failed in ParseLock#doParse() java.io.IOException: Stream closed #4899 + */ + @Test + void loadMultithreadedFromJar() throws IOException, InterruptedException { + Path jarPath = prepareCustomJar(); + String classpath = jarPath.toString(); + + int numberOfThreads = 2; + + final CyclicBarrier waitForClosed = new CyclicBarrier(numberOfThreads); + final Semaphore grabResource = new Semaphore(1); + final List caughtExceptions = new ArrayList<>(); + + class ThreadRunnable extends Thread { + private final int number; + + ThreadRunnable(int number) { + super("Thread" + number); + this.number = number; + } + + @Override + public void run() { + try (ClasspathClassLoader loader = new ClasspathClassLoader(classpath, null)) { + // Make sure, the threads get the resource stream one after another, so that the + // underlying Jar File is definitively cached (if caching is enabled). + grabResource.acquire(); + InputStream stream; + try { + stream = loader.getResourceAsStream(CUSTOM_JAR_RESOURCE); + } finally { + grabResource.release(); + } + try (InputStream in = stream) { + assertNotNull(in); + if (number > 0) { + // all except the first thread should wait until the first thread is finished + // and has closed the ClasspathClassLoader + waitForClosed.await(); + } + String s = IOUtil.readToString(in, StandardCharsets.UTF_8); + assertEquals(CUSTOM_JAR_RESOURCE_CONTENT, s); + } + } catch (Exception e) { + caughtExceptions.add(e); + throw new RuntimeException(e); + } finally { + try { + if (number == 0) { + // signal the other waiting threads to continue. Here, we have closed + // already the ClasspathClassLoader. + waitForClosed.await(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (BrokenBarrierException e) { + throw new RuntimeException(e); + } + } + } + } + + List threads = new ArrayList<>(numberOfThreads); + for (int i = 0; i < numberOfThreads; i++) { + threads.add(new ThreadRunnable(i)); + } + + threads.forEach(Thread::start); + for (Thread thread : threads) { + thread.join(); + } + + assertTrue(caughtExceptions.isEmpty()); + } + /** * Verifies, that we load the class files from the runtime image of the correct java home. * This tests multiple versions, in order to avoid that the test accidentally is successful when diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/ClassStub.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/ClassStub.java index 581b556399..813a6390f9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/ClassStub.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/ClassStub.java @@ -99,6 +99,9 @@ final class ClassStub implements JClassSymbol, AsmStub, AnnotationOwner { } else { return false; } + } catch (IOException e) { + // add a bit more info to the exception + throw new IOException("While loading class from " + loader, e); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/Loader.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/Loader.java index 8289601568..0eb7b64328 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/Loader.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/asm/Loader.java @@ -50,7 +50,7 @@ abstract class Loader { @Override public String toString() { - return "(StreamLoader for " + name + ")"; + return "StreamLoader(for " + name + ")"; } } } From a65975bf755fffa0233535aeb59c8c4fd0d5a3a1 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 11 Apr 2024 13:12:21 +0200 Subject: [PATCH 17/24] Add @mkolesnikov as a contributor --- .all-contributorsrc | 9 ++ docs/pages/pmd/projectdocs/credits.md | 123 +++++++++++++------------- 2 files changed, 72 insertions(+), 60 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 8148d99831..4166d23161 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -7453,6 +7453,15 @@ "contributions": [ "code" ] + }, + { + "login": "mkolesnikov", + "name": "Michael Kolesnikov", + "avatar_url": "https://avatars.githubusercontent.com/u/754163?v=4", + "profile": "https://github.com/mkolesnikov", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/docs/pages/pmd/projectdocs/credits.md b/docs/pages/pmd/projectdocs/credits.md index b1f89ce196..0e6b9270e0 100644 --- a/docs/pages/pmd/projectdocs/credits.md +++ b/docs/pages/pmd/projectdocs/credits.md @@ -514,548 +514,551 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Michael Dombrowski
      Michael Dombrowski

      🐛 Michael Hausegger
      Michael Hausegger

      🐛 Michael Hoefer
      Michael Hoefer

      🐛 + Michael Kolesnikov
      Michael Kolesnikov

      🐛 Michael Möbius
      Michael Möbius

      🐛 Michael N. Lipp
      Michael N. Lipp

      🐛 - Michael Pellegrini
      Michael Pellegrini

      🐛 + Michael Pellegrini
      Michael Pellegrini

      🐛 Michal Kordas
      Michal Kordas

      🐛 Michał Borek
      Michał Borek

      🐛 Michał Kuliński
      Michał Kuliński

      🐛 Miguel Núñez Díaz-Montes
      Miguel Núñez Díaz-Montes

      🐛 Mihai Ionut
      Mihai Ionut

      🐛 Mikhail Kuchma
      Mikhail Kuchma

      🐛 - Mirek Hankus
      Mirek Hankus

      🐛 + Mirek Hankus
      Mirek Hankus

      🐛 Mladjan Gadzic
      Mladjan Gadzic

      🐛 MrAngry52
      MrAngry52

      🐛 Muminur Choudhury
      Muminur Choudhury

      🐛 Mykhailo Palahuta
      Mykhailo Palahuta

      💻 🐛 Nagendra Kumar Singh
      Nagendra Kumar Singh

      🐛 Nahuel Barrios
      Nahuel Barrios

      🐛 - Nakul Sharma
      Nakul Sharma

      🐛 + Nakul Sharma
      Nakul Sharma

      🐛 Nathan Braun
      Nathan Braun

      🐛 Nathan Reynolds
      Nathan Reynolds

      🐛 Nathan Reynolds
      Nathan Reynolds

      🐛 Nathanaël
      Nathanaël

      🐛 Naveen
      Naveen

      💻 Nazdravi
      Nazdravi

      🐛 - Neha-Dhonde
      Neha-Dhonde

      🐛 + Neha-Dhonde
      Neha-Dhonde

      🐛 Nicholas Doyle
      Nicholas Doyle

      🐛 Nick Butcher
      Nick Butcher

      🐛 Nico Gallinal
      Nico Gallinal

      🐛 Nicola Dal Maso
      Nicola Dal Maso

      🐛 Nicolas Filotto
      Nicolas Filotto

      💻 Nicolas Vervelle
      Nicolas Vervelle

      🐛 - Nicolas Vuillamy
      Nicolas Vuillamy

      📖 + Nicolas Vuillamy
      Nicolas Vuillamy

      📖 Nikita Chursin
      Nikita Chursin

      🐛 Niklas Baudy
      Niklas Baudy

      🐛 Nikolas Havrikov
      Nikolas Havrikov

      🐛 Nilesh Virkar
      Nilesh Virkar

      🐛 Nimit Patel
      Nimit Patel

      🐛 Niranjan Harpale
      Niranjan Harpale

      🐛 - Nirvik Patel
      Nirvik Patel

      💻 + Nirvik Patel
      Nirvik Patel

      💻 Noah Sussman
      Noah Sussman

      🐛 Noah0120
      Noah0120

      🐛 Noam Tamim
      Noam Tamim

      🐛 Noel Grandin
      Noel Grandin

      🐛 Olaf Haalstra
      Olaf Haalstra

      🐛 Oleg Andreych
      Oleg Andreych

      💻 🐛 - Oleg Pavlenko
      Oleg Pavlenko

      🐛 + Oleg Pavlenko
      Oleg Pavlenko

      🐛 Oleksii Dykov
      Oleksii Dykov

      💻 🐛 Oliver Eikemeier
      Oliver Eikemeier

      🐛 Oliver Siegmar
      Oliver Siegmar

      💵 Olivier Parent
      Olivier Parent

      💻 🐛 Ollie Abbey
      Ollie Abbey

      💻 🐛 OverDrone
      OverDrone

      🐛 - Ozan Gulle
      Ozan Gulle

      💻 🐛 + Ozan Gulle
      Ozan Gulle

      💻 🐛 PUNEET JAIN
      PUNEET JAIN

      🐛 Parbati Bose
      Parbati Bose

      🐛 Paul Berg
      Paul Berg

      🐛 Paul Guyot
      Paul Guyot

      💻 Pavel Bludov
      Pavel Bludov

      🐛 Pavel Mička
      Pavel Mička

      🐛 - Pedro Nuno Santos
      Pedro Nuno Santos

      🐛 + Pedro Nuno Santos
      Pedro Nuno Santos

      🐛 Pedro Rijo
      Pedro Rijo

      🐛 Pelisse Romain
      Pelisse Romain

      💻 📖 🐛 Per Abich
      Per Abich

      💻 Pete Davids
      Pete Davids

      🐛 Peter Bruin
      Peter Bruin

      🐛 Peter Chittum
      Peter Chittum

      💻 🐛 - Peter Cudmore
      Peter Cudmore

      🐛 + Peter Cudmore
      Peter Cudmore

      🐛 Peter Kasson
      Peter Kasson

      🐛 Peter Kofler
      Peter Kofler

      🐛 Peter Paul Bakker
      Peter Paul Bakker

      💻 Peter Rader
      Peter Rader

      🐛 Pham Hai Trung
      Pham Hai Trung

      🐛 Philip Graf
      Philip Graf

      💻 🐛 - Philip Hachey
      Philip Hachey

      🐛 + Philip Hachey
      Philip Hachey

      🐛 Philippe Ozil
      Philippe Ozil

      🐛 Phinehas Artemix
      Phinehas Artemix

      🐛 Phokham Nonava
      Phokham Nonava

      🐛 Pim van der Loos
      Pim van der Loos

      💻 ⚠️ Piotr Szymański
      Piotr Szymański

      🐛 Piotrek Żygieło
      Piotrek Żygieło

      💻 🐛 📖 - Pranay Jaiswal
      Pranay Jaiswal

      🐛 + Pranay Jaiswal
      Pranay Jaiswal

      🐛 Prasad Kamath
      Prasad Kamath

      🐛 Prasanna
      Prasanna

      🐛 Presh-AR
      Presh-AR

      🐛 Puneet1726
      Puneet1726

      🐛 Rafael Cortês
      Rafael Cortês

      🐛 RaheemShaik999
      RaheemShaik999

      🐛 - RajeshR
      RajeshR

      💻 🐛 + RajeshR
      RajeshR

      💻 🐛 Ramachandra Mohan
      Ramachandra Mohan

      🐛 Ramel0921
      Ramel0921

      🐛 Raquel Pau
      Raquel Pau

      🐛 Ravikiran Janardhana
      Ravikiran Janardhana

      🐛 Reda Benhemmouche
      Reda Benhemmouche

      🐛 Renato Oliveira
      Renato Oliveira

      💻 🐛 - Rich DiCroce
      Rich DiCroce

      🐛 + Rich DiCroce
      Rich DiCroce

      🐛 Richard Corfield
      Richard Corfield

      💻 Richard Corfield
      Richard Corfield

      🐛 💻 Riot R1cket
      Riot R1cket

      🐛 Rishabh Jain
      Rishabh Jain

      🐛 RishabhDeep Singh
      RishabhDeep Singh

      🐛 Robbie Martinus
      Robbie Martinus

      💻 🐛 - Robert Henry
      Robert Henry

      🐛 + Robert Henry
      Robert Henry

      🐛 Robert Mihaly
      Robert Mihaly

      🐛 Robert Painsi
      Robert Painsi

      🐛 Robert Russell
      Robert Russell

      🐛 Robert Sösemann
      Robert Sösemann

      💻 📖 📢 🐛 Robert Whitebit
      Robert Whitebit

      🐛 Robin Richtsfeld
      Robin Richtsfeld

      🐛 - Robin Stocker
      Robin Stocker

      💻 🐛 + Robin Stocker
      Robin Stocker

      💻 🐛 Robin Wils
      Robin Wils

      🐛 RochusOest
      RochusOest

      🐛 Rodolfo Noviski
      Rodolfo Noviski

      🐛 Rodrigo Casara
      Rodrigo Casara

      🐛 Rodrigo Fernandes
      Rodrigo Fernandes

      🐛 Roman Salvador
      Roman Salvador

      💻 🐛 - Ronald Blaschke
      Ronald Blaschke

      🐛 + Ronald Blaschke
      Ronald Blaschke

      🐛 Róbert Papp
      Róbert Papp

      🐛 Saikat Sengupta
      Saikat Sengupta

      🐛 Saksham Handu
      Saksham Handu

      🐛 Saladoc
      Saladoc

      🐛 Salesforce Bob Lightning
      Salesforce Bob Lightning

      🐛 Sam Carlberg
      Sam Carlberg

      🐛 - Sashko
      Sashko

      💻 + Sashko
      Sashko

      💻 Satoshi Kubo
      Satoshi Kubo

      🐛 Scott Kennedy
      Scott Kennedy

      🐛 Scott Wells
      Scott Wells

      🐛 💻 Scrates1
      Scrates1

      🐛 💻 Scrsloota
      Scrsloota

      💻 Sebastian Bögl
      Sebastian Bögl

      🐛 - Sebastian Davids
      Sebastian Davids

      🐛 + Sebastian Davids
      Sebastian Davids

      🐛 Sebastian Schuberth
      Sebastian Schuberth

      🐛 Sebastian Schwarz
      Sebastian Schwarz

      🐛 Seren
      Seren

      🐛 💻 Sergey Gorbaty
      Sergey Gorbaty

      🐛 Sergey Kozlov
      Sergey Kozlov

      🐛 Sergey Yanzin
      Sergey Yanzin

      💻 🐛 - Seth Wilcox
      Seth Wilcox

      💻 + Seth Wilcox
      Seth Wilcox

      💻 Shai Bennathan
      Shai Bennathan

      🐛 💻 Shubham
      Shubham

      💻 🐛 Simon Abykov
      Simon Abykov

      💻 🐛 Simon Xiao
      Simon Xiao

      🐛 Srinivasan Venkatachalam
      Srinivasan Venkatachalam

      🐛 Stanislav Gromov
      Stanislav Gromov

      🐛 - Stanislav Myachenkov
      Stanislav Myachenkov

      💻 + Stanislav Myachenkov
      Stanislav Myachenkov

      💻 Stefan Birkner
      Stefan Birkner

      🐛 Stefan Bohn
      Stefan Bohn

      🐛 Stefan Endrullis
      Stefan Endrullis

      🐛 Stefan Klöss-Schuster
      Stefan Klöss-Schuster

      🐛 Stefan Wolf
      Stefan Wolf

      🐛 Stephan H. Wissel
      Stephan H. Wissel

      🐛 - Stephen
      Stephen

      🐛 + Stephen
      Stephen

      🐛 Stephen Friedrich
      Stephen Friedrich

      🐛 Steve Babula
      Steve Babula

      💻 Steven Stearns
      Steven Stearns

      🐛 💻 Stexxe
      Stexxe

      🐛 Stian Lågstad
      Stian Lågstad

      🐛 StuartClayton5
      StuartClayton5

      🐛 - Supun Arunoda
      Supun Arunoda

      🐛 + Supun Arunoda
      Supun Arunoda

      🐛 Suren Abrahamyan
      Suren Abrahamyan

      🐛 Suvashri
      Suvashri

      📖 SwatiBGupta1110
      SwatiBGupta1110

      🐛 SyedThoufich
      SyedThoufich

      🐛 Szymon Sasin
      Szymon Sasin

      🐛 T-chuangxin
      T-chuangxin

      🐛 - TERAI Atsuhiro
      TERAI Atsuhiro

      🐛 + TERAI Atsuhiro
      TERAI Atsuhiro

      🐛 TIOBE Software
      TIOBE Software

      💻 🐛 Tarush Singh
      Tarush Singh

      💻 Taylor Smock
      Taylor Smock

      🐛 Techeira Damián
      Techeira Damián

      💻 🐛 Ted Husted
      Ted Husted

      🐛 TehBakker
      TehBakker

      🐛 - The Gitter Badger
      The Gitter Badger

      🐛 + The Gitter Badger
      The Gitter Badger

      🐛 Theodoor
      Theodoor

      🐛 Thiago Henrique Hüpner
      Thiago Henrique Hüpner

      🐛 Thibault Meyer
      Thibault Meyer

      🐛 Thomas Güttler
      Thomas Güttler

      🐛 Thomas Jones-Low
      Thomas Jones-Low

      🐛 Thomas Smith
      Thomas Smith

      💻 🐛 - ThrawnCA
      ThrawnCA

      🐛 + ThrawnCA
      ThrawnCA

      🐛 Thu Vo
      Thu Vo

      🐛 Thunderforge
      Thunderforge

      💻 🐛 Tim van der Lippe
      Tim van der Lippe

      🐛 Tobias Weimer
      Tobias Weimer

      💻 🐛 Tom Copeland
      Tom Copeland

      🐛 💻 📖 Tom Daly
      Tom Daly

      🐛 - Tomer Figenblat
      Tomer Figenblat

      🐛 + Tomer Figenblat
      Tomer Figenblat

      🐛 Tomi De Lucca
      Tomi De Lucca

      💻 🐛 Torsten Kleiber
      Torsten Kleiber

      🐛 TrackerSB
      TrackerSB

      🐛 Tyson Stewart
      Tyson Stewart

      🐛 Ullrich Hafner
      Ullrich Hafner

      🐛 Utku Cuhadaroglu
      Utku Cuhadaroglu

      💻 🐛 - Valentin Brandl
      Valentin Brandl

      🐛 + Valentin Brandl
      Valentin Brandl

      🐛 Valeria
      Valeria

      🐛 Valery Yatsynovich
      Valery Yatsynovich

      📖 Vasily Anisimov
      Vasily Anisimov

      🐛 Vibhor Goyal
      Vibhor Goyal

      🐛 Vickenty Fesunov
      Vickenty Fesunov

      🐛 Victor Noël
      Victor Noël

      🐛 - Vincent Galloy
      Vincent Galloy

      💻 + Vincent Galloy
      Vincent Galloy

      💻 Vincent HUYNH
      Vincent HUYNH

      🐛 Vincent Maurin
      Vincent Maurin

      🐛 Vincent Privat
      Vincent Privat

      🐛 Vishhwas
      Vishhwas

      🐛 Vitaly
      Vitaly

      🐛 Vitaly Polonetsky
      Vitaly Polonetsky

      🐛 - Vojtech Polivka
      Vojtech Polivka

      🐛 + Vojtech Polivka
      Vojtech Polivka

      🐛 Vsevolod Zholobov
      Vsevolod Zholobov

      🐛 Vyom Yadav
      Vyom Yadav

      💻 Wang Shidong
      Wang Shidong

      🐛 Waqas Ahmed
      Waqas Ahmed

      🐛 Wayne J. Earl
      Wayne J. Earl

      🐛 Wchenghui
      Wchenghui

      🐛 - Wener
      Wener

      💻 + Wener
      Wener

      💻 Will Winder
      Will Winder

      🐛 William Brockhus
      William Brockhus

      💻 🐛 Wilson Kurniawan
      Wilson Kurniawan

      🐛 Wim Deblauwe
      Wim Deblauwe

      🐛 Woongsik Choi
      Woongsik Choi

      🐛 XenoAmess
      XenoAmess

      💻 🐛 - Yang
      Yang

      💻 + Yang
      Yang

      💻 YaroslavTER
      YaroslavTER

      🐛 Yasar Shaikh
      Yasar Shaikh

      💻 Young Chan
      Young Chan

      💻 🐛 YuJin Kim
      YuJin Kim

      🐛 Yuri Dolzhenko
      Yuri Dolzhenko

      🐛 Yurii Dubinka
      Yurii Dubinka

      🐛 - Zoltan Farkas
      Zoltan Farkas

      🐛 + Zoltan Farkas
      Zoltan Farkas

      🐛 Zustin
      Zustin

      🐛 aaronhurst-google
      aaronhurst-google

      🐛 💻 alexmodis
      alexmodis

      🐛 andreoss
      andreoss

      🐛 andrey81inmd
      andrey81inmd

      💻 🐛 anicoara
      anicoara

      🐛 - arunprasathav
      arunprasathav

      🐛 + arunprasathav
      arunprasathav

      🐛 asiercamara
      asiercamara

      🐛 astillich-igniti
      astillich-igniti

      💻 avesolovksyy
      avesolovksyy

      🐛 avishvat
      avishvat

      🐛 avivmu
      avivmu

      🐛 axelbarfod1
      axelbarfod1

      🐛 - b-3-n
      b-3-n

      🐛 + b-3-n
      b-3-n

      🐛 balbhadra9
      balbhadra9

      🐛 base23de
      base23de

      🐛 bergander
      bergander

      🐛 💻 berkam
      berkam

      💻 🐛 breizh31
      breizh31

      🐛 caesarkim
      caesarkim

      🐛 - carolyujing
      carolyujing

      🐛 + carolyujing
      carolyujing

      🐛 cbfiddle
      cbfiddle

      🐛 cesares-basilico
      cesares-basilico

      🐛 chrite
      chrite

      🐛 ciufudean
      ciufudean

      📖 cobratbq
      cobratbq

      🐛 coladict
      coladict

      🐛 - cosmoJFH
      cosmoJFH

      🐛 + cosmoJFH
      cosmoJFH

      🐛 cristalp
      cristalp

      🐛 crunsk
      crunsk

      🐛 cwholmes
      cwholmes

      🐛 cyberjj999
      cyberjj999

      🐛 cyw3
      cyw3

      🐛 📖 d1ss0nanz
      d1ss0nanz

      🐛 - dague1
      dague1

      📖 + dague1
      dague1

      📖 dalizi007
      dalizi007

      💻 danbrycefairsailcom
      danbrycefairsailcom

      🐛 dariansanity
      dariansanity

      🐛 darrenmiliband
      darrenmiliband

      🐛 davidburstrom
      davidburstrom

      🐛 dbirkman-paloalto
      dbirkman-paloalto

      🐛 - deepak-patra
      deepak-patra

      🐛 + deepak-patra
      deepak-patra

      🐛 dependabot[bot]
      dependabot[bot]

      💻 🐛 dinesh150
      dinesh150

      🐛 diziaq
      diziaq

      🐛 dreaminpast123
      dreaminpast123

      🐛 duanyanan
      duanyanan

      🐛 dutt-sanjay
      dutt-sanjay

      🐛 - dylanleung
      dylanleung

      🐛 + dylanleung
      dylanleung

      🐛 dzeigler
      dzeigler

      🐛 eant60
      eant60

      🐛 ekkirala
      ekkirala

      🐛 emersonmoura
      emersonmoura

      🐛 emouty
      emouty

      💻 eugenepugach
      eugenepugach

      🐛 - fairy
      fairy

      🐛 + fairy
      fairy

      🐛 filiprafalowicz
      filiprafalowicz

      💻 flxbl-io
      flxbl-io

      💵 foxmason
      foxmason

      🐛 frankegabor
      frankegabor

      🐛 frankl
      frankl

      🐛 freafrea
      freafrea

      🐛 - fsapatin
      fsapatin

      🐛 + fsapatin
      fsapatin

      🐛 gracia19
      gracia19

      🐛 guo fei
      guo fei

      🐛 gurmsc5
      gurmsc5

      🐛 gwilymatgearset
      gwilymatgearset

      💻 🐛 haigsn
      haigsn

      🐛 hemanshu070
      hemanshu070

      🐛 - henrik242
      henrik242

      🐛 + henrik242
      henrik242

      🐛 hongpuwu
      hongpuwu

      🐛 hvbtup
      hvbtup

      💻 🐛 igniti GmbH
      igniti GmbH

      🐛 ilovezfs
      ilovezfs

      🐛 itaigilo
      itaigilo

      🐛 jakivey32
      jakivey32

      🐛 - jbennett2091
      jbennett2091

      🐛 + jbennett2091
      jbennett2091

      🐛 jcamerin
      jcamerin

      🐛 jkeener1
      jkeener1

      🐛 jmetertea
      jmetertea

      🐛 johnra2
      johnra2

      💻 josemanuelrolon
      josemanuelrolon

      💻 🐛 kabroxiko
      kabroxiko

      💻 🐛 - karwer
      karwer

      🐛 + karwer
      karwer

      🐛 kaulonline
      kaulonline

      🐛 kdaemonv
      kdaemonv

      🐛 kdebski85
      kdebski85

      🐛 💻 kenji21
      kenji21

      💻 🐛 kfranic
      kfranic

      🐛 khalidkh
      khalidkh

      🐛 - koalalam
      koalalam

      🐛 + koalalam
      koalalam

      🐛 krzyk
      krzyk

      🐛 lasselindqvist
      lasselindqvist

      🐛 lgemeinhardt
      lgemeinhardt

      🐛 lihuaib
      lihuaib

      🐛 liqingjun123
      liqingjun123

      🐛 lonelyma1021
      lonelyma1021

      🐛 - lpeddy
      lpeddy

      🐛 + lpeddy
      lpeddy

      🐛 lujiefsi
      lujiefsi

      💻 lukelukes
      lukelukes

      💻 lyriccoder
      lyriccoder

      🐛 marcelmore
      marcelmore

      🐛 matchbox
      matchbox

      🐛 matthiaskraaz
      matthiaskraaz

      🐛 - meandonlyme
      meandonlyme

      🐛 + meandonlyme
      meandonlyme

      🐛 mikesive
      mikesive

      🐛 milossesic
      milossesic

      🐛 mluckam
      mluckam

      💻 mohan-chinnappan-n
      mohan-chinnappan-n

      💻 mriddell95
      mriddell95

      🐛 mrlzh
      mrlzh

      🐛 - msloan
      msloan

      🐛 + msloan
      msloan

      🐛 mucharlaravalika
      mucharlaravalika

      🐛 mvenneman
      mvenneman

      🐛 nareshl119
      nareshl119

      🐛 nicolas-harraudeau-sonarsource
      nicolas-harraudeau-sonarsource

      🐛 noerremark
      noerremark

      🐛 novsirion
      novsirion

      🐛 - nwcm
      nwcm

      📖 🐛 💻 + nwcm
      nwcm

      📖 🐛 💻 oggboy
      oggboy

      🐛 oinume
      oinume

      🐛 orimarko
      orimarko

      💻 🐛 pacvz
      pacvz

      💻 pallavi agarwal
      pallavi agarwal

      🐛 parksungrin
      parksungrin

      🐛 - patpatpat123
      patpatpat123

      🐛 + patpatpat123
      patpatpat123

      🐛 patriksevallius
      patriksevallius

      🐛 pbrajesh1
      pbrajesh1

      🐛 phoenix384
      phoenix384

      🐛 piotrszymanski-sc
      piotrszymanski-sc

      💻 plan3d
      plan3d

      🐛 poojasix
      poojasix

      🐛 - prabhushrikant
      prabhushrikant

      🐛 + prabhushrikant
      prabhushrikant

      🐛 pujitha8783
      pujitha8783

      🐛 r-r-a-j
      r-r-a-j

      🐛 raghujayjunk
      raghujayjunk

      🐛 rajeshveera
      rajeshveera

      🐛 rajeswarreddy88
      rajeswarreddy88

      🐛 recdevs
      recdevs

      🐛 - reudismam
      reudismam

      💻 🐛 + reudismam
      reudismam

      💻 🐛 rijkt
      rijkt

      🐛 rillig-tk
      rillig-tk

      🐛 rmohan20
      rmohan20

      💻 🐛 rnveach
      rnveach

      🐛 rxmicro
      rxmicro

      🐛 ryan-gustafson
      ryan-gustafson

      💻 🐛 - sabi0
      sabi0

      🐛 + sabi0
      sabi0

      🐛 scais
      scais

      🐛 screamingfrog
      screamingfrog

      💵 sebbASF
      sebbASF

      🐛 sergeygorbaty
      sergeygorbaty

      💻 shilko2013
      shilko2013

      🐛 shiomiyan
      shiomiyan

      📖 - simeonKondr
      simeonKondr

      🐛 + simeonKondr
      simeonKondr

      🐛 snajberk
      snajberk

      🐛 sniperrifle2004
      sniperrifle2004

      🐛 snuyanzin
      snuyanzin

      🐛 💻 soyodream
      soyodream

      🐛 sratz
      sratz

      🐛 stonio
      stonio

      🐛 - sturton
      sturton

      💻 🐛 + sturton
      sturton

      💻 🐛 sudharmohan
      sudharmohan

      🐛 suruchidawar
      suruchidawar

      🐛 svenfinitiv
      svenfinitiv

      🐛 tashiscool
      tashiscool

      🐛 test-git-hook
      test-git-hook

      🐛 testation21
      testation21

      💻 🐛 - thanosa
      thanosa

      🐛 + thanosa
      thanosa

      🐛 tiandiyixian
      tiandiyixian

      🐛 tobwoerk
      tobwoerk

      🐛 tprouvot
      tprouvot

      🐛 💻 trentchilders
      trentchilders

      🐛 triandicAnt
      triandicAnt

      🐛 trishul14
      trishul14

      🐛 - tsui
      tsui

      🐛 + tsui
      tsui

      🐛 wangzitom12306
      wangzitom12306

      🐛 winhkey
      winhkey

      🐛 witherspore
      witherspore

      🐛 wjljack
      wjljack

      🐛 wuchiuwong
      wuchiuwong

      🐛 xingsong
      xingsong

      🐛 - xioayuge
      xioayuge

      🐛 + xioayuge
      xioayuge

      🐛 xnYi9wRezm
      xnYi9wRezm

      💻 🐛 xuanuy
      xuanuy

      🐛 xyf0921
      xyf0921

      🐛 yalechen-cyw3
      yalechen-cyw3

      🐛 yasuharu-sato
      yasuharu-sato

      🐛 zenglian
      zenglian

      🐛 - zgrzyt93
      zgrzyt93

      💻 🐛 + zgrzyt93
      zgrzyt93

      💻 🐛 zh3ng
      zh3ng

      🐛 zt_soft
      zt_soft

      🐛 ztt79
      ztt79

      🐛 zzzzfeng
      zzzzfeng

      🐛 Árpád Magosányi
      Árpád Magosányi

      🐛 任贵杰
      任贵杰

      🐛 + + 茅延安
      茅延安

      💻 From 6ad857788080189755534111c0827c76cc75a8b7 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 11 Apr 2024 15:59:29 +0200 Subject: [PATCH 18/24] [doc] Fix rule doc generator under Windows --- .../java/net/sourceforge/pmd/doc/internal/RuleDocGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-doc/src/main/java/net/sourceforge/pmd/doc/internal/RuleDocGenerator.java b/pmd-doc/src/main/java/net/sourceforge/pmd/doc/internal/RuleDocGenerator.java index 5ad83287e4..24eba979bb 100644 --- a/pmd-doc/src/main/java/net/sourceforge/pmd/doc/internal/RuleDocGenerator.java +++ b/pmd-doc/src/main/java/net/sourceforge/pmd/doc/internal/RuleDocGenerator.java @@ -656,7 +656,7 @@ public class RuleDocGenerator { Files.walkFileTree(root, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - String path = file.toString(); + String path = RuleSetUtils.normalizeForwardSlashes(file.toString()); if (path.contains("src")) { String foundRuleClass = null; From 64eeeec0005b621e0ba29c835707e595b19aeb6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 12 Apr 2024 16:34:23 +0200 Subject: [PATCH 19/24] Add test for #4947 --- .../sourceforge/pmd/lang/java/ast/ParserCornersTest.java | 5 +++++ .../net/sourceforge/pmd/lang/java/ast/ASTLiteralTest.kt | 8 +++++++- .../pmd/lang/java/ast/testdata/Issue4947TextBlock.java | 7 +++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/testdata/Issue4947TextBlock.java diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java index a6713bfd44..805bd68f15 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java @@ -362,4 +362,9 @@ class ParserCornersTest extends BaseJavaTreeDumpTest { void testGitHubBug1780() { doTest("GitHubBug1780OuterClass"); } + + @Test + void testGithubBug4947() { + doTest("testdata/Issue4947TextBlock"); + } } diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTLiteralTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTLiteralTest.kt index 92d43cdffd..13de3376c1 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTLiteralTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTLiteralTest.kt @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.java.ast import io.kotest.matchers.shouldBe -import net.sourceforge.pmd.lang.test.ast.shouldBe import net.sourceforge.pmd.lang.java.ast.JavaVersion.* import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Earliest import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Latest @@ -15,6 +14,7 @@ import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind.* import net.sourceforge.pmd.lang.test.ast.NodeSpec import net.sourceforge.pmd.lang.test.ast.ValuedNodeSpec +import net.sourceforge.pmd.lang.test.ast.shouldBe import net.sourceforge.pmd.lang.test.ast.shouldHaveText /** @@ -135,6 +135,12 @@ $delim $delim """.testTextBlock() + + """ + $delim + x$delim + """.testTextBlock() + } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/testdata/Issue4947TextBlock.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/testdata/Issue4947TextBlock.java new file mode 100644 index 0000000000..9c63076bfd --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/testdata/Issue4947TextBlock.java @@ -0,0 +1,7 @@ +package net.sourceforge.pmd.lang.java.ast.testdata; + +public class Issue4947TextBlock { + String digits = """ + x"""; + // STR."\{1}"); +} From 967c16561ab36e75304f43e5d2a5040f5a808f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Fri, 12 Apr 2024 17:59:10 +0200 Subject: [PATCH 20/24] Refactor how string templates are parsed Fix #4947 --- pmd-java/etc/grammar/Java.jjt | 224 ++++++------------ .../pmd/lang/java/ast/ASTTemplate.java | 2 +- .../lang/java/ast/ASTTemplateExpression.java | 5 +- .../lang/java/ast/ASTTemplateFragment.java | 7 +- .../pmd/lang/java/ast/ParserCornersTest.java | 2 +- .../java21p/Jep430_StringTemplates.txt | 96 ++++++-- .../java22p/Jep459_StringTemplates.txt | 96 ++++++-- 7 files changed, 248 insertions(+), 184 deletions(-) diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index c29ac79910..d7f641bffd 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -324,7 +324,6 @@ import java.util.Set; import java.util.Map; import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.JavaParserImplTokenManager.TokenContext; import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind; class JavaParserImpl { @@ -602,37 +601,56 @@ class JavaParserImpl { */ private boolean inExplicitConstructorInvoc = false; - /** - * Keeps track of the current token context. This is needed to resolve the ambiguities around String Templates: - * A closing bracket (RBRACE) might close a block or might be the start of a STRING_TEMPLATE_MID/END token. - * - * @see JEP 430: String Templates (Preview) - */ - private Deque tokenContexts = new ArrayDeque(); - TokenContext determineTokenContext() { - if (tokenContexts.isEmpty()) { - return TokenContext.BLOCK; - } - return tokenContexts.peek(); - } } PARSER_END(JavaParserImpl) TOKEN_MGR_DECLS : { protected List comments = new ArrayList(); - - enum TokenContext { STRING_TEMPLATE, TEXT_BLOCK_TEMPLATE, BLOCK; } + private Deque savedTemplateKind = new ArrayDeque(); + private Deque braceDepthInTemplate = new ArrayDeque(); + private int braceDepthCurrentTemplate; + private boolean templateKind; private static final java.util.regex.Pattern TEXT_BLOCK_TEMPLATE_END_PATTERN = java.util.regex.Pattern.compile("^}[^\"]*\"\"\""); private static final java.util.regex.Pattern STRING_TEMPLATE_MID_OR_END_PATTERN = java.util.regex.Pattern.compile("^}(?:[^\"\\\\\n\r]|\\\\(?:[ntbrfs\\\\'\"]|[0-7][0-7]?|[0-3][0-7][0-7]))*(\\{|\")"); - private TokenContext determineContext() { - return parser.determineTokenContext(); + private void pushBrace() { + braceDepthCurrentTemplate++; + } + private void pushTemplate(boolean isTextBlockTemplate) { + braceDepthInTemplate.push(braceDepthCurrentTemplate); + savedTemplateKind.push(templateKind); + templateKind = isTextBlockTemplate; + braceDepthCurrentTemplate = 1; + } + private boolean isInTemplate() { + return braceDepthCurrentTemplate > 0; + } + private void popBrace() { + if (!isInTemplate()) + return; + if (--braceDepthCurrentTemplate == 0) { + // this brace ends the template + popTemplate(); + } + } + private void popTemplate() { + boolean isInTextBlockTemplate = templateKind; + if (!braceDepthInTemplate.isEmpty()) { + braceDepthCurrentTemplate = braceDepthInTemplate.pop(); + templateKind = savedTemplateKind.pop(); + if (isInTextBlockTemplate) { + SwitchTo(JavaTokenKinds.IN_TEXT_BLOCK_LITERAL); + } else { + SwitchTo(JavaTokenKinds.IN_STRING_TEMPLATE); + } + } else { + SwitchTo(JavaTokenKinds.DEFAULT); + } } - private net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken rereadTokenAs(int kind, int length) { input_stream.backup(lengthOfMatch); try { @@ -834,6 +852,8 @@ TOKEN : ) > | < #TEXT_BLOCK_CHARACTER: ~["\\"] | | ("\\")? > +| < #STRING_FRAGMENT: ()* > +| < STRING_TEMPLATE_BEGIN: "\"" "\\{" > { pushTemplate(false); } } /* TEXT BLOCKS */ @@ -845,9 +865,9 @@ MORE : } -TOKEN : -{ +TOKEN : { : DEFAULT +| { pushTemplate(true); }: DEFAULT } @@ -856,6 +876,13 @@ MORE : < > } + +TOKEN: +{ + < STRING_TEMPLATE_END: "\"" > : DEFAULT +| < STRING_TEMPLATE_MID: "\\{" > { pushTemplate(false); } : DEFAULT +} + /* IDENTIFIERS */ TOKEN : @@ -993,8 +1020,8 @@ TOKEN : { < LPAREN: "(" > | < RPAREN: ")" > -| < LBRACE: "{" > -| < RBRACE: "}" > +| < LBRACE: "{" > { pushBrace(); } +| < RBRACE: "}" > { popBrace(); } | < LBRACKET: "[" > | < RBRACKET: "]" > | < SEMICOLON: ";" > @@ -1055,87 +1082,6 @@ TOKEN : | < GT: ">" > } -/* FRAGMENTS */ - -// Note: The fragments introduce ambiguity with other token productions, especially the separator token "}" (RBRACE). -// In order to produce the correct token sequence, the ambiguity needs to be resolved using the context. -// That means, that STRING_TEMPLATE_MID/END and TEXT_BLOCK_TEMPLATE_MID/END could actually be a closing bracket ("}"). -// Additionally, a STRING_TEMPLATE_MID could be a TEXT_BLOCK_TEMPLATE_MID and the other way round. -// See JLS 3.13 Fragments (Java 21 Preview and Java 22 Preview) - -TOKEN : -{ - < STRING_TEMPLATE_BEGIN: "\"" "\\{" > -| < STRING_TEMPLATE_MID: "}" "\\{" > -{ - { - TokenContext ctx = determineContext(); - switch (ctx) { - case TEXT_BLOCK_TEMPLATE: - jjmatchedKind = TEXT_BLOCK_TEMPLATE_MID; - matchedToken = jjFillToken(); - break; - case BLOCK: - matchedToken = handleBlock(); - break; - } - } -} -| < STRING_TEMPLATE_END: "}" "\"" > -{ - { - TokenContext ctx = determineContext(); - if (ctx == TokenContext.BLOCK) { - matchedToken = handleBlock(); - } - } -} -| < #STRING_FRAGMENT: ()* > - -| < TEXT_BLOCK_TEMPLATE_BEGIN: "\"\"\"" ()* "\\{" > -| < TEXT_BLOCK_TEMPLATE_MID: "}" "\\{" > -{ - { - TokenContext ctx = determineContext(); - switch (ctx) { - case STRING_TEMPLATE: { - java.util.regex.Matcher m = STRING_TEMPLATE_MID_OR_END_PATTERN.matcher(matchedToken.getImage()); - if (m.find()) { - int kind = STRING_TEMPLATE_END; - if ("\\{".equals(m.group(1))) { - kind = STRING_TEMPLATE_MID; - } - matchedToken = rereadTokenAs(kind, m.end()); - } - break; - } - case TEXT_BLOCK_TEMPLATE: { - // Note: TEXT_BLOCK_FRAGMENT is not really correct and might match """ as part of TEXT_BLOCK_TEMPLATE_MID - // instead of TEXT_BLOCK_TEMPLATE_END. In case this happens, this is corrected here. - java.util.regex.Matcher m = TEXT_BLOCK_TEMPLATE_END_PATTERN.matcher(matchedToken.getImage()); - if (m.find()) { - matchedToken = rereadTokenAs(TEXT_BLOCK_TEMPLATE_END, m.end()); - } - break; - } - case BLOCK: - matchedToken = handleBlock(); - break; - } - } - } -| < TEXT_BLOCK_TEMPLATE_END: "}" "\"\"\"" > -{ - { - TokenContext ctx = determineContext(); - if (ctx == TokenContext.BLOCK) { - matchedToken = handleBlock(); - } - } -} -| < #TEXT_BLOCK_FRAGMENT: ()* > -} - /***************************************** * THE JAVA LANGUAGE GRAMMAR STARTS HERE * *****************************************/ @@ -1304,11 +1250,11 @@ void EnumDeclaration(): void EnumBody(): {} { - "{" { tokenContexts.push(TokenContext.BLOCK); } + "{" [ EnumConstant() ( LOOKAHEAD(2) "," EnumConstant() )* ] [ "," { jjtThis.setTrailingComma(); } ] [ ";" { jjtThis.setSeparatorSemi(); } ( ClassOrInterfaceBodyDeclaration() )* ] - "}" { tokenContexts.pop(); } + "}" } void EnumConstant(): @@ -1351,9 +1297,9 @@ void RecordComponent(): void RecordBody(): {} { - "{" { tokenContexts.push(TokenContext.BLOCK); } + "{" ( RecordBodyDeclaration() )* - "}" { tokenContexts.pop(); } + "}" } void RecordBodyDeclaration() #void : @@ -1393,9 +1339,9 @@ void TypeParameter(): void ClassOrInterfaceBody() #ClassBody: {} { - "{" { tokenContexts.push(TokenContext.BLOCK); } + "{" ( ClassOrInterfaceBodyDeclaration() )* - "}" { tokenContexts.pop(); } + "}" } void ClassOrInterfaceBodyDeclaration() #void: @@ -1456,9 +1402,9 @@ void VariableInitializer() #void: void ArrayInitializer() : {} { - "{" { tokenContexts.push(TokenContext.BLOCK); } + "{" [ VariableInitializer() ( LOOKAHEAD(2) "," VariableInitializer() )* ] [ "," ] - "}" { tokenContexts.pop(); } + "}" } void MethodDeclaration() : @@ -1523,7 +1469,7 @@ void ConstructorDeclaration() : private void ConstructorBlock() #Block: {} { - "{" { tokenContexts.push(TokenContext.BLOCK); } + "{" ( LOOKAHEAD(ExplicitConstructorInvocation()) ExplicitConstructorInvocation() | @@ -1531,7 +1477,7 @@ private void ConstructorBlock() #Block: [ LOOKAHEAD(ExplicitConstructorInvocation()) ExplicitConstructorInvocation() ] ) ( BlockStatement() )* - "}" { tokenContexts.pop(); } + "}" } void ExplicitConstructorInvocation() : @@ -2224,7 +2170,7 @@ void PrimaryStep2() #void: // "super" alone is not a valid expression ("." MemberSelector() | MethodReference()) | MemberSelector() - | {forceExprContext();} TemplateArgument() #TemplateExpression(2) + | {forceExprContext();} Template() #TemplateExpression(2) ) // catches the case where the ambig name is the start of an array type | LOOKAHEAD("@" | "[" "]") {forceTypeContext();} Dims() #ArrayType(2) (MethodReference() | "." "class" #ClassLiteral(1)) @@ -2329,45 +2275,33 @@ boolean LambdaParameterType() #void : | FormalParamType() { return false; } } -void TemplateArgument() #void : -{} -{ - Template() - | StringLiteral() -} - void Template() : {} { StringTemplate() | TextBlockTemplate() + | #TemplateFragment + | #TemplateFragment } void StringTemplate() #void : {} { - { tokenContexts.push(TokenContext.STRING_TEMPLATE); } - - { jjtThis.setContent(getToken(0).getImage()); } #TemplateFragment + #TemplateFragment EmbeddedExpression() - ( { jjtThis.setContent(getToken(0).getImage()); } #TemplateFragment EmbeddedExpression() )* - { jjtThis.setContent(getToken(0).getImage()); } #TemplateFragment - - { tokenContexts.pop(); } + (LOOKAHEAD(2) ( ) #TemplateFragment EmbeddedExpression() )* + ( ) #TemplateFragment } void TextBlockTemplate() #void : {} { - { tokenContexts.push(TokenContext.TEXT_BLOCK_TEMPLATE); } - { jjtThis.setContent(getToken(0).getImage()); } #TemplateFragment + #TemplateFragment EmbeddedExpression() - ( { jjtThis.setContent(getToken(0).getImage()); } #TemplateFragment EmbeddedExpression() )* - { jjtThis.setContent(getToken(0).getImage()); } #TemplateFragment - - { tokenContexts.pop(); } + (LOOKAHEAD(2) ( ) #TemplateFragment EmbeddedExpression() )* + ( ) #TemplateFragment } void EmbeddedExpression() #void : @@ -2533,9 +2467,9 @@ void LabeledStatement() : void Block() : {} { - "{" { tokenContexts.push(TokenContext.BLOCK); } + "{" ( BlockStatement() )* - "}" { tokenContexts.pop(); } + "}" } void BlockStatement() #void: @@ -2666,12 +2600,12 @@ void SwitchStatement(): void SwitchBlock() #void: {} { - "{" { tokenContexts.push(TokenContext.BLOCK); } + "{" ( LOOKAHEAD(SwitchLabel() ":") (SwitchFallthroughBranch())+ | (SwitchArrowBranch())* ) - "}" { tokenContexts.pop(); } + "}" } void SwitchArrowBranch(): @@ -2966,9 +2900,9 @@ void MemberValue() #void: void MemberValueArrayInitializer(): {} { - "{" { tokenContexts.push(TokenContext.BLOCK); } + "{" (MemberValue() ( LOOKAHEAD(2) "," MemberValue() )*)? [ "," ] - "}" { tokenContexts.pop(); } + "}" } /* @@ -2993,9 +2927,9 @@ void AnnotationTypeDeclaration(): void AnnotationTypeBody(): {} { - "{" { tokenContexts.push(TokenContext.BLOCK); } + "{" ( AnnotationTypeMemberDeclaration() )* - "}" { tokenContexts.pop(); } + "}" } void AnnotationTypeMemberDeclaration() #void: @@ -3040,9 +2974,9 @@ void ModuleDeclaration(): [LOOKAHEAD({isKeyword("open")}) {jjtThis.setOpen(true);}] LOOKAHEAD({isKeyword("module")}) ModuleName() - "{" { tokenContexts.push(TokenContext.BLOCK); } + "{" (ModuleDirective())* - "}" { tokenContexts.pop(); } + "}" } void ModuleDirective() #void: diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplate.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplate.java index 13decf984d..3ce54c7cad 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplate.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplate.java @@ -7,7 +7,7 @@ package net.sourceforge.pmd.lang.java.ast; import net.sourceforge.pmd.annotation.Experimental; /** - * This is a Java 21/22 Preview feature. + * The template of a {@link ASTTemplateExpression}. This is a Java 21/22 Preview feature. * *
        *
      diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplateExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplateExpression.java
      index ef2b512b09..73c00d841b 100644
      --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplateExpression.java
      +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplateExpression.java
      @@ -8,12 +8,11 @@ import net.sourceforge.pmd.annotation.Experimental;
       import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr;
       
       /**
      - * This is a Java 21/22 Preview feature.
      + * A string template expression. This is a Java 21/22 Preview feature.
        *
        * 
        *
      - * TemplateExpression ::= ({@link ASTVariableAccess VariableAccess} | {@link ASTFieldAccess FieldAccess})
      - *                        ({@link ASTTemplate Template} | {@link ASTStringLiteral StringLiteral})
      + * TemplateExpression ::= ({@link ASTVariableAccess VariableAccess} | {@link ASTFieldAccess FieldAccess}) {@link ASTTemplate Template}
        *
        * 
      * diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplateFragment.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplateFragment.java index 2ecf0cc3fc..2f88b7903b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplateFragment.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTemplateFragment.java @@ -21,7 +21,6 @@ import net.sourceforge.pmd.annotation.Experimental; */ @Experimental("String templates is a Java 21/22 Preview feature") public final class ASTTemplateFragment extends AbstractJavaNode { - private String content; ASTTemplateFragment(int i) { super(i); @@ -33,11 +32,7 @@ public final class ASTTemplateFragment extends AbstractJavaNode { } public String getContent() { - return content; - } - - void setContent(String content) { - this.content = content; + return getText().toString(); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java index 805bd68f15..fec719a586 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java @@ -365,6 +365,6 @@ class ParserCornersTest extends BaseJavaTreeDumpTest { @Test void testGithubBug4947() { - doTest("testdata/Issue4947TextBlock"); + java15.parseResource("testdata/Issue4947TextBlock.java"); } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep430_StringTemplates.txt b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep430_StringTemplates.txt index 3a4560f3a4..a1124094d6 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep430_StringTemplates.txt +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep430_StringTemplates.txt @@ -308,7 +308,7 @@ | +- ModifierList[@EffectiveModifiers = "{static}", @ExplicitModifiers = "{static}"] | +- VoidType[] | +- FormalParameters[@Empty = true, @Size = 0] - | +- Block[@Empty = false, @Size = 7, @containsComment = false] + | +- Block[@Empty = false, @Size = 10, @containsComment = true] | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] | | +- ClassType[@FullyQualified = false, @SimpleName = "String"] @@ -353,20 +353,87 @@ | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "address", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "1 Maple Drive, Anytown", @Empty = false, @Image = "\"1 Maple Drive, Anytown\"", @Length = 22, @LiteralText = "\"1 Maple Drive, Anytown\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] + | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] + | | +- ClassType[@FullyQualified = false, @SimpleName = "String"] + | | +- VariableDeclarator[@Initializer = true, @Name = "json"] + | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "json", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] + | | +- TemplateExpression[@CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false, @StringTemplate = true] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "STR", @Name = "STR", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- Template[] + | | +- TemplateFragment[@Content = "\"\"\"\n {\n \"name\": \"\\{"] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "name", @Name = "name", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- TemplateFragment[@Content = "}\",\n \"phone\": \"\\{"] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "phone", @Name = "phone", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- TemplateFragment[@Content = "}\",\n \"address\": \"\\{"] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "address", @Name = "address", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- TemplateFragment[@Content = "}\"\n }\n \"\"\""] + | +- LocalClassStatement[] + | | +- RecordDeclaration[@Abstract = false, @Annotation = false, @Anonymous = false, @BinaryName = "Jep430_StringTemplates$1Rectangle", @CanonicalName = null, @EffectiveVisibility = Visibility.V_LOCAL, @Enum = false, @Final = true, @Interface = false, @Local = true, @Nested = false, @PackageName = "", @Record = true, @RegularClass = false, @RegularInterface = false, @SimpleName = "Rectangle", @Static = true, @TopLevel = false, @Visibility = Visibility.V_LOCAL] + | | +- ModifierList[@EffectiveModifiers = "{static, final}", @ExplicitModifiers = "{}"] + | | +- RecordComponentList[@Empty = false, @Size = 3, @Varargs = false] + | | | +- RecordComponent[@EffectiveVisibility = Visibility.V_LOCAL, @Varargs = false, @Visibility = Visibility.V_PRIVATE] + | | | | +- ModifierList[@EffectiveModifiers = "{private, final}", @ExplicitModifiers = "{}"] + | | | | +- ClassType[@FullyQualified = false, @SimpleName = "String"] + | | | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = true, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = false, @Name = "name", @PatternBinding = false, @RecordComponent = true, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_PRIVATE] + | | | +- RecordComponent[@EffectiveVisibility = Visibility.V_LOCAL, @Varargs = false, @Visibility = Visibility.V_PRIVATE] + | | | | +- ModifierList[@EffectiveModifiers = "{private, final}", @ExplicitModifiers = "{}"] + | | | | +- PrimitiveType[@Kind = PrimitiveTypeKind.DOUBLE] + | | | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = true, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = false, @Name = "width", @PatternBinding = false, @RecordComponent = true, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_PRIVATE] + | | | +- RecordComponent[@EffectiveVisibility = Visibility.V_LOCAL, @Varargs = false, @Visibility = Visibility.V_PRIVATE] + | | | +- ModifierList[@EffectiveModifiers = "{private, final}", @ExplicitModifiers = "{}"] + | | | +- PrimitiveType[@Kind = PrimitiveTypeKind.DOUBLE] + | | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = true, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = false, @Name = "height", @PatternBinding = false, @RecordComponent = true, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_PRIVATE] + | | +- RecordBody[@Empty = false, @Size = 1] + | | +- MethodDeclaration[@Abstract = false, @Arity = 0, @EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @Name = "area", @Overridden = false, @Static = false, @Varargs = false, @Visibility = Visibility.V_PACKAGE, @Void = false] + | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] + | | +- PrimitiveType[@Kind = PrimitiveTypeKind.DOUBLE] + | | +- FormalParameters[@Empty = true, @Size = 0] + | | +- Block[@Empty = false, @Size = 1, @containsComment = false] + | | +- ReturnStatement[] + | | +- InfixExpression[@CompileTimeConstant = false, @Operator = BinaryOp.MUL, @ParenthesisDepth = 0, @Parenthesized = false] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "width", @Name = "width", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "height", @Name = "height", @ParenthesisDepth = 0, @Parenthesized = false] + | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] + | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] + | | +- ArrayType[@ArrayDepth = 1] + | | | +- ClassType[@FullyQualified = false, @SimpleName = "Rectangle"] + | | | +- ArrayDimensions[@Empty = false, @Size = 1] + | | | +- ArrayTypeDim[@Varargs = false] + | | +- VariableDeclarator[@Initializer = true, @Name = "zone"] + | | +- VariableId[@ArrayType = true, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "zone", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] + | | +- ArrayAllocation[@ArrayDepth = 1, @CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false] + | | +- ArrayType[@ArrayDepth = 1] + | | | +- ClassType[@FullyQualified = false, @SimpleName = "Rectangle"] + | | | +- ArrayDimensions[@Empty = false, @Size = 1] + | | | +- ArrayTypeDim[@Varargs = false] + | | +- ArrayInitializer[@CompileTimeConstant = false, @Length = 3, @ParenthesisDepth = 0, @Parenthesized = false] + | | +- ConstructorCall[@AnonymousClass = false, @CompileTimeConstant = false, @DiamondTypeArgs = false, @MethodName = "new", @ParenthesisDepth = 0, @Parenthesized = false, @QualifiedInstanceCreation = false] + | | | +- ClassType[@FullyQualified = false, @SimpleName = "Rectangle"] + | | | +- ArgumentList[@Empty = false, @Size = 3] + | | | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "Alfa", @Empty = false, @Image = "\"Alfa\"", @Length = 4, @LiteralText = "\"Alfa\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] + | | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "17.8", @IntLiteral = false, @Integral = false, @LiteralText = "17.8", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 17.8, @ValueAsFloat = 17.8, @ValueAsInt = 17, @ValueAsLong = 17] + | | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "31.4", @IntLiteral = false, @Integral = false, @LiteralText = "31.4", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 31.4, @ValueAsFloat = 31.4, @ValueAsInt = 31, @ValueAsLong = 31] + | | +- ConstructorCall[@AnonymousClass = false, @CompileTimeConstant = false, @DiamondTypeArgs = false, @MethodName = "new", @ParenthesisDepth = 0, @Parenthesized = false, @QualifiedInstanceCreation = false] + | | | +- ClassType[@FullyQualified = false, @SimpleName = "Rectangle"] + | | | +- ArgumentList[@Empty = false, @Size = 3] + | | | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "Bravo", @Empty = false, @Image = "\"Bravo\"", @Length = 5, @LiteralText = "\"Bravo\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] + | | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "9.6", @IntLiteral = false, @Integral = false, @LiteralText = "9.6", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 9.6, @ValueAsFloat = 9.6, @ValueAsInt = 9, @ValueAsLong = 9] + | | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "12.4", @IntLiteral = false, @Integral = false, @LiteralText = "12.4", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 12.4, @ValueAsFloat = 12.4, @ValueAsInt = 12, @ValueAsLong = 12] + | | +- ConstructorCall[@AnonymousClass = false, @CompileTimeConstant = false, @DiamondTypeArgs = false, @MethodName = "new", @ParenthesisDepth = 0, @Parenthesized = false, @QualifiedInstanceCreation = false] + | | +- ClassType[@FullyQualified = false, @SimpleName = "Rectangle"] + | | +- ArgumentList[@Empty = false, @Size = 3] + | | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "Charlie", @Empty = false, @Image = "\"Charlie\"", @Length = 7, @LiteralText = "\"Charlie\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] + | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "7.1", @IntLiteral = false, @Integral = false, @LiteralText = "7.1", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 7.1, @ValueAsFloat = 7.1, @ValueAsInt = 7, @ValueAsLong = 7] + | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "11.23", @IntLiteral = false, @Integral = false, @LiteralText = "11.23", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 11.23, @ValueAsFloat = 11.23, @ValueAsInt = 11, @ValueAsLong = 11] + | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] | +- ClassType[@FullyQualified = false, @SimpleName = "String"] - | +- VariableDeclarator[@Initializer = true, @Name = "json"] - | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "json", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] + | +- VariableDeclarator[@Initializer = true, @Name = "table"] + | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "table", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | +- TemplateExpression[@CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false, @StringTemplate = true] | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "STR", @Name = "STR", @ParenthesisDepth = 0, @Parenthesized = false] | +- Template[] - | +- TemplateFragment[@Content = "\"\"\"\n {\n \"name\": \"\\{"] - | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "name", @Name = "name", @ParenthesisDepth = 0, @Parenthesized = false] - | +- TemplateFragment[@Content = "}\",\n \"phone\": \"\\{"] - | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "phone", @Name = "phone", @ParenthesisDepth = 0, @Parenthesized = false] - | +- TemplateFragment[@Content = "}\",\n \"address\": \"\\{"] - | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "address", @Name = "address", @ParenthesisDepth = 0, @Parenthesized = false] - | +- TemplateFragment[@Content = "}\"\n }\n \"\"\";\n /*\n | \"\"\"\n | {\n | \"name\": \"Joan Smith\",\n | \"phone\": \"555-123-4567\",\n | \"address\": \"1 Maple Drive, Anytown\"\n | }\n | \"\"\"\n */\n\n record Rectangle(String name, double width, double height) {\n double area() {\n return width * height;\n }\n }\n Rectangle[] zone = new Rectangle[] {\n new Rectangle(\"Alfa\", 17.8, 31.4),\n new Rectangle(\"Bravo\", 9.6, 12.4),\n new Rectangle(\"Charlie\", 7.1, 11.23),\n };\n String table = STR.\"\"\"\n Description Width Height Area\n \\{"] + | +- TemplateFragment[@Content = "\"\"\"\n Description Width Height Area\n \\{"] | +- FieldAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "name", @Name = "name", @ParenthesisDepth = 0, @Parenthesized = false] | | +- ArrayAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false] | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "zone", @Name = "zone", @ParenthesisDepth = 0, @Parenthesized = false] @@ -452,9 +519,9 @@ | +- ModifierList[@EffectiveModifiers = "{static}", @ExplicitModifiers = "{static}"] | +- VoidType[] | +- FormalParameters[@Empty = true, @Size = 0] - | +- Block[@Empty = false, @Size = 4, @containsComment = false] + | +- Block[@Empty = false, @Size = 4, @containsComment = true] | +- LocalClassStatement[] - | | +- RecordDeclaration[@Abstract = false, @Annotation = false, @Anonymous = false, @BinaryName = "Jep430_StringTemplates$1Rectangle", @CanonicalName = null, @EffectiveVisibility = Visibility.V_LOCAL, @Enum = false, @Final = true, @Interface = false, @Local = true, @Nested = false, @PackageName = "", @Record = true, @RegularClass = false, @RegularInterface = false, @SimpleName = "Rectangle", @Static = true, @TopLevel = false, @Visibility = Visibility.V_LOCAL] + | | +- RecordDeclaration[@Abstract = false, @Annotation = false, @Anonymous = false, @BinaryName = "Jep430_StringTemplates$2Rectangle", @CanonicalName = null, @EffectiveVisibility = Visibility.V_LOCAL, @Enum = false, @Final = true, @Interface = false, @Local = true, @Nested = false, @PackageName = "", @Record = true, @RegularClass = false, @RegularInterface = false, @SimpleName = "Rectangle", @Static = true, @TopLevel = false, @Visibility = Visibility.V_LOCAL] | | +- ModifierList[@EffectiveModifiers = "{static, final}", @ExplicitModifiers = "{}"] | | +- RecordComponentList[@Empty = false, @Size = 3, @Varargs = false] | | | +- RecordComponent[@EffectiveVisibility = Visibility.V_LOCAL, @Varargs = false, @Visibility = Visibility.V_PRIVATE] @@ -654,7 +721,7 @@ | +- ModifierList[@EffectiveModifiers = "{static}", @ExplicitModifiers = "{static}"] | +- VoidType[] | +- FormalParameters[@Empty = true, @Size = 0] - | +- Block[@Empty = false, @Size = 3, @containsComment = false] + | +- Block[@Empty = false, @Size = 3, @containsComment = true] | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] | | +- ClassType[@FullyQualified = false, @SimpleName = "String"] @@ -662,7 +729,8 @@ | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "s1", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | | +- TemplateExpression[@CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false, @StringTemplate = true] | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "STR", @Name = "STR", @ParenthesisDepth = 0, @Parenthesized = false] - | | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "Welcome to your account", @Empty = false, @Image = "\"Welcome to your account\"", @Length = 23, @LiteralText = "\"Welcome to your account\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] + | | +- Template[] + | | +- TemplateFragment[@Content = "\"Welcome to your account\""] | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] | | +- ClassType[@FullyQualified = false, @SimpleName = "User"] diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java22p/Jep459_StringTemplates.txt b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java22p/Jep459_StringTemplates.txt index c8a5b31ddf..c65df30abd 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java22p/Jep459_StringTemplates.txt +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java22p/Jep459_StringTemplates.txt @@ -308,7 +308,7 @@ | +- ModifierList[@EffectiveModifiers = "{static}", @ExplicitModifiers = "{static}"] | +- VoidType[] | +- FormalParameters[@Empty = true, @Size = 0] - | +- Block[@Empty = false, @Size = 7, @containsComment = false] + | +- Block[@Empty = false, @Size = 10, @containsComment = true] | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] | | +- ClassType[@FullyQualified = false, @SimpleName = "String"] @@ -353,20 +353,87 @@ | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "address", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "1 Maple Drive, Anytown", @Empty = false, @Image = "\"1 Maple Drive, Anytown\"", @Length = 22, @LiteralText = "\"1 Maple Drive, Anytown\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] + | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] + | | +- ClassType[@FullyQualified = false, @SimpleName = "String"] + | | +- VariableDeclarator[@Initializer = true, @Name = "json"] + | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "json", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] + | | +- TemplateExpression[@CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false, @StringTemplate = true] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "STR", @Name = "STR", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- Template[] + | | +- TemplateFragment[@Content = "\"\"\"\n {\n \"name\": \"\\{"] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "name", @Name = "name", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- TemplateFragment[@Content = "}\",\n \"phone\": \"\\{"] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "phone", @Name = "phone", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- TemplateFragment[@Content = "}\",\n \"address\": \"\\{"] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "address", @Name = "address", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- TemplateFragment[@Content = "}\"\n }\n \"\"\""] + | +- LocalClassStatement[] + | | +- RecordDeclaration[@Abstract = false, @Annotation = false, @Anonymous = false, @BinaryName = "Jep459_StringTemplates$1Rectangle", @CanonicalName = null, @EffectiveVisibility = Visibility.V_LOCAL, @Enum = false, @Final = true, @Interface = false, @Local = true, @Nested = false, @PackageName = "", @Record = true, @RegularClass = false, @RegularInterface = false, @SimpleName = "Rectangle", @Static = true, @TopLevel = false, @Visibility = Visibility.V_LOCAL] + | | +- ModifierList[@EffectiveModifiers = "{static, final}", @ExplicitModifiers = "{}"] + | | +- RecordComponentList[@Empty = false, @Size = 3, @Varargs = false] + | | | +- RecordComponent[@EffectiveVisibility = Visibility.V_LOCAL, @Varargs = false, @Visibility = Visibility.V_PRIVATE] + | | | | +- ModifierList[@EffectiveModifiers = "{private, final}", @ExplicitModifiers = "{}"] + | | | | +- ClassType[@FullyQualified = false, @SimpleName = "String"] + | | | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = true, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = false, @Name = "name", @PatternBinding = false, @RecordComponent = true, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_PRIVATE] + | | | +- RecordComponent[@EffectiveVisibility = Visibility.V_LOCAL, @Varargs = false, @Visibility = Visibility.V_PRIVATE] + | | | | +- ModifierList[@EffectiveModifiers = "{private, final}", @ExplicitModifiers = "{}"] + | | | | +- PrimitiveType[@Kind = PrimitiveTypeKind.DOUBLE] + | | | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = true, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = false, @Name = "width", @PatternBinding = false, @RecordComponent = true, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_PRIVATE] + | | | +- RecordComponent[@EffectiveVisibility = Visibility.V_LOCAL, @Varargs = false, @Visibility = Visibility.V_PRIVATE] + | | | +- ModifierList[@EffectiveModifiers = "{private, final}", @ExplicitModifiers = "{}"] + | | | +- PrimitiveType[@Kind = PrimitiveTypeKind.DOUBLE] + | | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = true, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = false, @Name = "height", @PatternBinding = false, @RecordComponent = true, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_PRIVATE] + | | +- RecordBody[@Empty = false, @Size = 1] + | | +- MethodDeclaration[@Abstract = false, @Arity = 0, @EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @Name = "area", @Overridden = false, @Static = false, @Varargs = false, @Visibility = Visibility.V_PACKAGE, @Void = false] + | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] + | | +- PrimitiveType[@Kind = PrimitiveTypeKind.DOUBLE] + | | +- FormalParameters[@Empty = true, @Size = 0] + | | +- Block[@Empty = false, @Size = 1, @containsComment = false] + | | +- ReturnStatement[] + | | +- InfixExpression[@CompileTimeConstant = false, @Operator = BinaryOp.MUL, @ParenthesisDepth = 0, @Parenthesized = false] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "width", @Name = "width", @ParenthesisDepth = 0, @Parenthesized = false] + | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "height", @Name = "height", @ParenthesisDepth = 0, @Parenthesized = false] + | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] + | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] + | | +- ArrayType[@ArrayDepth = 1] + | | | +- ClassType[@FullyQualified = false, @SimpleName = "Rectangle"] + | | | +- ArrayDimensions[@Empty = false, @Size = 1] + | | | +- ArrayTypeDim[@Varargs = false] + | | +- VariableDeclarator[@Initializer = true, @Name = "zone"] + | | +- VariableId[@ArrayType = true, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "zone", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] + | | +- ArrayAllocation[@ArrayDepth = 1, @CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false] + | | +- ArrayType[@ArrayDepth = 1] + | | | +- ClassType[@FullyQualified = false, @SimpleName = "Rectangle"] + | | | +- ArrayDimensions[@Empty = false, @Size = 1] + | | | +- ArrayTypeDim[@Varargs = false] + | | +- ArrayInitializer[@CompileTimeConstant = false, @Length = 3, @ParenthesisDepth = 0, @Parenthesized = false] + | | +- ConstructorCall[@AnonymousClass = false, @CompileTimeConstant = false, @DiamondTypeArgs = false, @MethodName = "new", @ParenthesisDepth = 0, @Parenthesized = false, @QualifiedInstanceCreation = false] + | | | +- ClassType[@FullyQualified = false, @SimpleName = "Rectangle"] + | | | +- ArgumentList[@Empty = false, @Size = 3] + | | | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "Alfa", @Empty = false, @Image = "\"Alfa\"", @Length = 4, @LiteralText = "\"Alfa\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] + | | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "17.8", @IntLiteral = false, @Integral = false, @LiteralText = "17.8", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 17.8, @ValueAsFloat = 17.8, @ValueAsInt = 17, @ValueAsLong = 17] + | | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "31.4", @IntLiteral = false, @Integral = false, @LiteralText = "31.4", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 31.4, @ValueAsFloat = 31.4, @ValueAsInt = 31, @ValueAsLong = 31] + | | +- ConstructorCall[@AnonymousClass = false, @CompileTimeConstant = false, @DiamondTypeArgs = false, @MethodName = "new", @ParenthesisDepth = 0, @Parenthesized = false, @QualifiedInstanceCreation = false] + | | | +- ClassType[@FullyQualified = false, @SimpleName = "Rectangle"] + | | | +- ArgumentList[@Empty = false, @Size = 3] + | | | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "Bravo", @Empty = false, @Image = "\"Bravo\"", @Length = 5, @LiteralText = "\"Bravo\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] + | | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "9.6", @IntLiteral = false, @Integral = false, @LiteralText = "9.6", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 9.6, @ValueAsFloat = 9.6, @ValueAsInt = 9, @ValueAsLong = 9] + | | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "12.4", @IntLiteral = false, @Integral = false, @LiteralText = "12.4", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 12.4, @ValueAsFloat = 12.4, @ValueAsInt = 12, @ValueAsLong = 12] + | | +- ConstructorCall[@AnonymousClass = false, @CompileTimeConstant = false, @DiamondTypeArgs = false, @MethodName = "new", @ParenthesisDepth = 0, @Parenthesized = false, @QualifiedInstanceCreation = false] + | | +- ClassType[@FullyQualified = false, @SimpleName = "Rectangle"] + | | +- ArgumentList[@Empty = false, @Size = 3] + | | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "Charlie", @Empty = false, @Image = "\"Charlie\"", @Length = 7, @LiteralText = "\"Charlie\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] + | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "7.1", @IntLiteral = false, @Integral = false, @LiteralText = "7.1", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 7.1, @ValueAsFloat = 7.1, @ValueAsInt = 7, @ValueAsLong = 7] + | | +- NumericLiteral[@Base = 10, @CompileTimeConstant = true, @DoubleLiteral = true, @FloatLiteral = false, @Image = "11.23", @IntLiteral = false, @Integral = false, @LiteralText = "11.23", @LongLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @ValueAsDouble = 11.23, @ValueAsFloat = 11.23, @ValueAsInt = 11, @ValueAsLong = 11] + | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] | +- ClassType[@FullyQualified = false, @SimpleName = "String"] - | +- VariableDeclarator[@Initializer = true, @Name = "json"] - | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "json", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] + | +- VariableDeclarator[@Initializer = true, @Name = "table"] + | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "table", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | +- TemplateExpression[@CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false, @StringTemplate = true] | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "STR", @Name = "STR", @ParenthesisDepth = 0, @Parenthesized = false] | +- Template[] - | +- TemplateFragment[@Content = "\"\"\"\n {\n \"name\": \"\\{"] - | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "name", @Name = "name", @ParenthesisDepth = 0, @Parenthesized = false] - | +- TemplateFragment[@Content = "}\",\n \"phone\": \"\\{"] - | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "phone", @Name = "phone", @ParenthesisDepth = 0, @Parenthesized = false] - | +- TemplateFragment[@Content = "}\",\n \"address\": \"\\{"] - | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "address", @Name = "address", @ParenthesisDepth = 0, @Parenthesized = false] - | +- TemplateFragment[@Content = "}\"\n }\n \"\"\";\n /*\n | \"\"\"\n | {\n | \"name\": \"Joan Smith\",\n | \"phone\": \"555-123-4567\",\n | \"address\": \"1 Maple Drive, Anytown\"\n | }\n | \"\"\"\n */\n\n record Rectangle(String name, double width, double height) {\n double area() {\n return width * height;\n }\n }\n Rectangle[] zone = new Rectangle[] {\n new Rectangle(\"Alfa\", 17.8, 31.4),\n new Rectangle(\"Bravo\", 9.6, 12.4),\n new Rectangle(\"Charlie\", 7.1, 11.23),\n };\n String table = STR.\"\"\"\n Description Width Height Area\n \\{"] + | +- TemplateFragment[@Content = "\"\"\"\n Description Width Height Area\n \\{"] | +- FieldAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "name", @Name = "name", @ParenthesisDepth = 0, @Parenthesized = false] | | +- ArrayAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false] | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "zone", @Name = "zone", @ParenthesisDepth = 0, @Parenthesized = false] @@ -452,9 +519,9 @@ | +- ModifierList[@EffectiveModifiers = "{static}", @ExplicitModifiers = "{static}"] | +- VoidType[] | +- FormalParameters[@Empty = true, @Size = 0] - | +- Block[@Empty = false, @Size = 4, @containsComment = false] + | +- Block[@Empty = false, @Size = 4, @containsComment = true] | +- LocalClassStatement[] - | | +- RecordDeclaration[@Abstract = false, @Annotation = false, @Anonymous = false, @BinaryName = "Jep459_StringTemplates$1Rectangle", @CanonicalName = null, @EffectiveVisibility = Visibility.V_LOCAL, @Enum = false, @Final = true, @Interface = false, @Local = true, @Nested = false, @PackageName = "", @Record = true, @RegularClass = false, @RegularInterface = false, @SimpleName = "Rectangle", @Static = true, @TopLevel = false, @Visibility = Visibility.V_LOCAL] + | | +- RecordDeclaration[@Abstract = false, @Annotation = false, @Anonymous = false, @BinaryName = "Jep459_StringTemplates$2Rectangle", @CanonicalName = null, @EffectiveVisibility = Visibility.V_LOCAL, @Enum = false, @Final = true, @Interface = false, @Local = true, @Nested = false, @PackageName = "", @Record = true, @RegularClass = false, @RegularInterface = false, @SimpleName = "Rectangle", @Static = true, @TopLevel = false, @Visibility = Visibility.V_LOCAL] | | +- ModifierList[@EffectiveModifiers = "{static, final}", @ExplicitModifiers = "{}"] | | +- RecordComponentList[@Empty = false, @Size = 3, @Varargs = false] | | | +- RecordComponent[@EffectiveVisibility = Visibility.V_LOCAL, @Varargs = false, @Visibility = Visibility.V_PRIVATE] @@ -654,7 +721,7 @@ | +- ModifierList[@EffectiveModifiers = "{static}", @ExplicitModifiers = "{static}"] | +- VoidType[] | +- FormalParameters[@Empty = true, @Size = 0] - | +- Block[@Empty = false, @Size = 3, @containsComment = false] + | +- Block[@Empty = false, @Size = 3, @containsComment = true] | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] | | +- ClassType[@FullyQualified = false, @SimpleName = "String"] @@ -662,7 +729,8 @@ | | +- VariableId[@ArrayType = false, @EffectiveVisibility = Visibility.V_LOCAL, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = false, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @LambdaParameter = false, @LocalVariable = true, @Name = "s1", @PatternBinding = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | | +- TemplateExpression[@CompileTimeConstant = false, @ParenthesisDepth = 0, @Parenthesized = false, @StringTemplate = true] | | +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Image = "STR", @Name = "STR", @ParenthesisDepth = 0, @Parenthesized = false] - | | +- StringLiteral[@CompileTimeConstant = true, @ConstValue = "Welcome to your account", @Empty = false, @Image = "\"Welcome to your account\"", @Length = 23, @LiteralText = "\"Welcome to your account\"", @ParenthesisDepth = 0, @Parenthesized = false, @TextBlock = false] + | | +- Template[] + | | +- TemplateFragment[@Content = "\"Welcome to your account\""] | +- LocalVariableDeclaration[@EffectiveVisibility = Visibility.V_LOCAL, @Final = false, @TypeInferred = false, @Visibility = Visibility.V_LOCAL] | | +- ModifierList[@EffectiveModifiers = "{}", @ExplicitModifiers = "{}"] | | +- ClassType[@FullyQualified = false, @SimpleName = "User"] From 4dcdfb0d4fc4a3c3bc4e386782129db4142b3c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 15 Apr 2024 15:21:57 +0200 Subject: [PATCH 21/24] Fix xpath expressions in documentation Fix #4901 --- docs/pages/pmd/userdocs/suppressing_warnings.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/pages/pmd/userdocs/suppressing_warnings.md b/docs/pages/pmd/userdocs/suppressing_warnings.md index 08ec4ddbc6..c67dd019b6 100644 --- a/docs/pages/pmd/userdocs/suppressing_warnings.md +++ b/docs/pages/pmd/userdocs/suppressing_warnings.md @@ -210,19 +210,27 @@ more violations than expected. Another example, to suppress violations occurring in classes whose name contains `Bean`: ```xml - + ``` You can also use regex for string comparison. The next example suppresses violations in classes ending with `Bean`: ```xml - + ``` -Note here the usage of the `./ancestor::` axis instead of `//`. The latter would match +Note here the usage of the `./ancestor-or-self::` axis instead of `//`. The latter would match any ClassDeclaration in the file, while the former matches only class declaration nodes that *enclose the violation node*, which is usually what you'd want. +Note the context node in this expression is the node the violation was reported on. Different +rules report on different nodes, for instance, {% rule java/bestpractices/UnusedFormalParameter %} reports +on the `ASTVariableId` for the parameter, and {% rule java/bestpractices/MissingOverride %} reports on +the `ASTMethodDeclaration` node. You have to take this into account when using the context node directly, +although many `violationSuppressXPath` expressions will check for some ancestor node like an enclosing class or method, +instead of testing the context node directly. +The nodes which a rule reports on can only be determined by looking at the implementation of the rule. + Note for XPath based suppression to work, you must know how to write an XPath query that matches the AST structure of the nodes of the violations you wish to suppress. XPath queries are explained in From 9f605552ee715a4250618e04c18059602df9f50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Mon, 15 Apr 2024 15:25:59 +0200 Subject: [PATCH 22/24] Add one example --- docs/pages/pmd/userdocs/suppressing_warnings.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/pages/pmd/userdocs/suppressing_warnings.md b/docs/pages/pmd/userdocs/suppressing_warnings.md index c67dd019b6..18b5f35888 100644 --- a/docs/pages/pmd/userdocs/suppressing_warnings.md +++ b/docs/pages/pmd/userdocs/suppressing_warnings.md @@ -213,6 +213,13 @@ Another example, to suppress violations occurring in classes whose name contains ``` +And an example to suppress violations occurring in `equals` or `hashCode` methods: +```xml + +``` +Note the use of a sequence comparison, which tests for true if any member of one sequence matches any other member of the other. +Here this test is true if the `@Name` attribute is any of the given names. + You can also use regex for string comparison. The next example suppresses violations in classes ending with `Bean`: ```xml From e0705e5b287e8a7ffab5e59cdea767b7286102d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 16 Apr 2024 17:34:12 -0300 Subject: [PATCH 23/24] Update changelog, refs #4961 --- docs/pages/release_notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 912c4ce1c5..76564e278f 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -26,6 +26,8 @@ This is a {{ site.pmd.release_type }} release. * cli * [#4791](https://github.com/pmd/pmd/issues/4791): \[cli] Could not find or load main class * [#4913](https://github.com/pmd/pmd/issues/4913): \[cli] cpd-gui closes immediately +* doc + * [#4901](https://github.com/pmd/pmd/issues/4901): \[doc] Improve documentation on usage of violationSuppressXPath * apex * [#4418](https://github.com/pmd/pmd/issues/4418): \[apex] ASTAnnotation.getImage() does not return value as written in the class * apex-errorprone From 1be81a55cd2bade8b3cfb0666564f67000403b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Mart=C3=ADn=20Sotuyo=20Dodero?= Date: Tue, 16 Apr 2024 17:36:32 -0300 Subject: [PATCH 24/24] Update changelog, refs #4947 --- docs/pages/release_notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 912c4ce1c5..438329c3fa 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -33,6 +33,7 @@ This is a {{ site.pmd.release_type }} release. * java * [#4899](https://github.com/pmd/pmd/issues/4899): \[java] Parsing failed in ParseLock#doParse() java.io.IOException: Stream closed * [#4902](https://github.com/pmd/pmd/issues/4902): \[java] "Bad intersection, unrelated class types" for Constable\[] and Enum\[] + * [#4947](https://github.com/pmd/pmd/issues/4947): \[java] Broken TextBlock parser * java-bestpractices * [#1084](https://github.com/pmd/pmd/issues/1084): \[java] Allow JUnitTestsShouldIncludeAssert to configure verification methods * [#4435](https://github.com/pmd/pmd/issues/4435): \[java] \[7.0-rc1] UnusedAssignment for used field