diff --git a/docs/pages/7_0_0_release_notes.md b/docs/pages/7_0_0_release_notes.md index de38d0b896..7893a725fa 100644 --- a/docs/pages/7_0_0_release_notes.md +++ b/docs/pages/7_0_0_release_notes.md @@ -46,6 +46,25 @@ Support for XPath versions 1.0, 1.0-compatibility was removed, support for XPath * The deprecated support for sequence-valued attributes is removed. Sequence-valued properties are still supported. * Refer to [the Saxonica documentation](https://www.saxonica.com/html/documentation/expressions/xpath31new.html) for an introduction to new features in XPath 3.1. + +#### Node stream API + +This version includes a powerful API to navigate trees, similar in usage to the Java 8 Stream API: +```java +node.descendants(ASTMethodCall.class) + .filter(m -> "toString".equals(m.getMethodName())) + .map(m -> m.getQualifier()) + .filter(q -> TypeTestUtil.isA(String.class, q)) + .foreach(System.out::println); +``` + +A pipeline like shown here traverses the tree lazily, which is more efficient than traversing eagerly to put all descendants in a list. It is also much easier to change than the old imperative way. + +To make this API as accessible as possible, the {% jdoc core::lang.ast.Node %} interface has been fitted with new methods producing node streams. Those methods replace previous tree traversal methods like `Node#findDescendantsOfType`. In all cases, they should be more efficient and more convenient. + +See {% jdoc core::lang.ast.NodeStream %} for more details. + + #### JavaScript support The JS specific parser options have been removed. The parser now always retains comments and uses version ES6. diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java index d3ab5b8066..d5e6ed3be6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java @@ -13,10 +13,11 @@ import java.util.Objects; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import net.sourceforge.pmd.annotation.DeprecatedUntil700; import net.sourceforge.pmd.lang.ast.NodeStream.DescendantNodeStream; import net.sourceforge.pmd.lang.ast.internal.StreamImpl; import net.sourceforge.pmd.lang.rule.xpath.Attribute; -import net.sourceforge.pmd.lang.rule.xpath.DeprecatedAttribute; +import net.sourceforge.pmd.lang.rule.xpath.NoAttribute; import net.sourceforge.pmd.lang.rule.xpath.XPathVersion; import net.sourceforge.pmd.lang.rule.xpath.impl.AttributeAxisIterator; import net.sourceforge.pmd.lang.rule.xpath.impl.XPathHandler; @@ -30,10 +31,14 @@ import net.sourceforge.pmd.util.DataMap.DataKey; * Root interface for all AST nodes. This interface provides only the API * shared by all AST implementations in PMD language modules. This includes for now: *
Every language implementation must publish a sub-interface of Node
@@ -59,7 +64,12 @@ public interface Node {
* Returns a string token, usually filled-in by the parser, which describes some textual characteristic of this
* node. This is usually an identifier, but you should check that using the Designer. On most nodes though, this
* method returns {@code null}.
+ *
+ * @deprecated Should be replaced with methods that have more specific
+ * names in node classes.
*/
+ @Deprecated
+ @DeprecatedUntil700
default String getImage() {
return null;
}
@@ -69,7 +79,11 @@ public interface Node {
* Returns true if this node's image is equal to the given string.
*
* @param image The image to check
+ *
+ * @deprecated See {@link #getImage()}
*/
+ @Deprecated
+ @DeprecatedUntil700
default boolean hasImageEqualTo(String image) {
return Objects.equals(getImage(), image);
}
@@ -116,7 +130,7 @@ public interface Node {
*
* @see DescendantNodeStream#crossFindBoundaries(boolean)
*/
- @DeprecatedAttribute
+ @NoAttribute
default boolean isFindBoundary() {
return false;
}
@@ -131,7 +145,11 @@ public interface Node {
* @param n how many ancestors to iterate over.
* @return the n-th parent or null.
* @throws IllegalArgumentException if {@code n} is negative or zero.
+ *
+ * @deprecated Use node stream methods: {@code node.ancestors().get(n-1)}
*/
+ @Deprecated
+ @DeprecatedUntil700
default Node getNthParent(int n) {
return ancestors().get(n - 1);
}
@@ -142,7 +160,11 @@ public interface Node {
* @param parentType Class literal of the type you want to find
* @param R acceptVisitor(AstVisitor super P, ? extends R> visitor, P data) {
return visitor.cannotVisit(this, data);
@@ -349,6 +414,8 @@ public interface Node {
/**
* Returns the first child of this node, or null if it doesn't exist.
+ *
+ * @since 7.0.0
*/
default @Nullable Node getFirstChild() {
return getNumChildren() > 0 ? getChild(0) : null;
@@ -357,12 +424,42 @@ public interface Node {
/**
* Returns the first last of this node, or null if it doesn't exist.
+ *
+ * @since 7.0.0
*/
default @Nullable Node getLastChild() {
return getNumChildren() > 0 ? getChild(getNumChildren() - 1) : null;
}
+ /**
+ * Returns the previous sibling of this node, or null if it does not exist.
+ *
+ * @since 7.0.0
+ */
+ default @Nullable Node getPreviousSibling() {
+ Node parent = getParent();
+ int idx = getIndexInParent();
+ if (parent != null && idx > 0) {
+ return parent.getChild(idx - 1);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the next sibling of this node, or null if it does not exist.
+ *
+ * @since 7.0.0
+ */
+ default @Nullable Node getNextSibling() {
+ Node parent = getParent();
+ int idx = getIndexInParent();
+ if (parent != null && idx < parent.getNumChildren()) {
+ return parent.getChild(idx + 1);
+ }
+ return null;
+ }
+
/**
* Returns a node stream containing only this node.
* {@link NodeStream#of(Node)} is a null-safe version
@@ -371,6 +468,7 @@ public interface Node {
* @return A node stream containing only this node
*
* @see NodeStream#of(Node)
+ * @since 7.0.0
*/
default NodeStream extends Node> asStream() {
return StreamImpl.singleton(this);
@@ -383,6 +481,7 @@ public interface Node {
* you'll probably want to use {@link #children(Class)}.
*
* @see NodeStream#children(Class)
+ * @since 7.0.0
*/
default NodeStream extends Node> children() {
return StreamImpl.children(this);
@@ -396,6 +495,7 @@ public interface Node {
* @return A node stream of the descendants of this node
*
* @see NodeStream#descendants()
+ * @since 7.0.0
*/
default DescendantNodeStream extends Node> descendants() {
return StreamImpl.descendants(this);
@@ -409,6 +509,7 @@ public interface Node {
* @return A node stream of the whole subtree topped by this node
*
* @see NodeStream#descendantsOrSelf()
+ * @since 7.0.0
*/
default DescendantNodeStream extends Node> descendantsOrSelf() {
return StreamImpl.descendantsOrSelf(this);
@@ -423,6 +524,7 @@ public interface Node {
* @return A node stream of the ancestors of this node
*
* @see NodeStream#ancestors()
+ * @since 7.0.0
*/
default NodeStream extends Node> ancestors() {
return StreamImpl.ancestors(this);
@@ -437,6 +539,7 @@ public interface Node {
* @return A stream of ancestors
*
* @see NodeStream#ancestorsOrSelf()
+ * @since 7.0.0
*/
default NodeStream extends Node> ancestorsOrSelf() {
return StreamImpl.ancestorsOrSelf(this);
@@ -453,11 +556,31 @@ public interface Node {
* @return A new node stream
*
* @see NodeStream#children(Class)
+ * @since 7.0.0
*/
default If you want to process this element as a node stream, use
+ * {@code asStream().firstChild(rClass)} instead, which returns
+ * a node stream.
+ *
+ * @param rClass Type of the child to find
+ * @param false, recursion stops for nodes for which
* {@link #isFindBoundary()} is true
* @return List of all matching descendants
+ *
+ * @deprecated Use node stream methods: {@code node.descendants(targetType).crossFindBoundaries(b).toList()}.
+ * Most usages don't really need a list though, eg you can iterate the node stream instead
*/
+ @Deprecated
+ @DeprecatedUntil700
default null if none found.
* @see #getFirstDescendantOfType(Class) if traversal of the entire tree is needed.
+ *
+ * @deprecated Use {@link #firstChild(Class)}
*/
+ @Deprecated
+ @DeprecatedUntil700
default null if none found.
+ *
+ * @deprecated Use node stream methods: {@code node.descendants(targetType).first()}.
*/
+ @Deprecated
+ @DeprecatedUntil700
default true if there is at least one descendant of the given type
+ *
+ * @deprecated Use node stream methods: {@code node.descendants(targetType).nonEmpty()}.
*/
+ @Deprecated
+ @DeprecatedUntil700
default