0
# AST Processing
1
2
The AST (Abstract Syntax Tree) Processing module provides comprehensive navigation, querying, and analysis capabilities for parsed source code structures. It offers tree traversal, node searching, XPath querying, and streaming operations for efficient AST manipulation.
3
4
## Capabilities
5
6
### Node Interface
7
8
Core interface for Abstract Syntax Tree nodes providing navigation, querying, and content access capabilities.
9
10
```java { .api }
11
/**
12
* Core interface for Abstract Syntax Tree nodes.
13
* Provides navigation, querying, and content access for parsed source code.
14
*/
15
public interface Node {
16
17
/**
18
* Get XPath node name for XPath queries
19
* @return Node name used in XPath expressions
20
*/
21
String getXPathNodeName();
22
23
/**
24
* Get parent node in the AST
25
* @return Parent Node, or null if this is the root
26
*/
27
Node getParent();
28
29
/**
30
* Get child node by index
31
* @param index Zero-based index of child
32
* @return Child Node at specified index
33
* @throws IndexOutOfBoundsException if index is invalid
34
*/
35
Node getChild(int index);
36
37
/**
38
* Get number of direct children
39
* @return Count of immediate child nodes
40
*/
41
int getNumChildren();
42
43
/**
44
* Get all direct children as list
45
* @return Unmodifiable list of child nodes
46
*/
47
List<? extends Node> getChildren();
48
49
/**
50
* Get index of this node within its parent's children
51
* @return Zero-based index in parent, or -1 if no parent
52
*/
53
int getIndexInParent();
54
55
/**
56
* Get first child node
57
* @return First child, or null if no children
58
*/
59
Node getFirstChild();
60
61
/**
62
* Get last child node
63
* @return Last child, or null if no children
64
*/
65
Node getLastChild();
66
67
/**
68
* Get next sibling node
69
* @return Next sibling, or null if this is the last child
70
*/
71
Node getNextSibling();
72
73
/**
74
* Get previous sibling node
75
* @return Previous sibling, or null if this is the first child
76
*/
77
Node getPreviousSibling();
78
79
/**
80
* Check if any descendant matches the given node type
81
* @param type Class of node type to search for
82
* @return true if at least one descendant of the type exists
83
*/
84
boolean hasDescendantOfType(Class<? extends Node> type);
85
86
/**
87
* Find all descendants of specific type
88
* @param type Class of node type to find
89
* @return List of all descendant nodes matching the type
90
*/
91
List<? extends Node> findDescendantsOfType(Class<? extends Node> type);
92
93
/**
94
* Find first descendant of specific type
95
* @param type Class of node type to find
96
* @return First descendant matching type, or null if none found
97
*/
98
Node getFirstDescendantOfType(Class<? extends Node> type);
99
100
/**
101
* Stream all descendants of this node
102
* @return NodeStream for efficient descendant traversal
103
*/
104
NodeStream<? extends Node> descendants();
105
106
/**
107
* Stream direct children of this node
108
* @return NodeStream for child iteration
109
*/
110
NodeStream<? extends Node> children();
111
112
/**
113
* Get text region in source file
114
* @return TextRegion with start/end positions
115
*/
116
TextRegion getTextRegion();
117
118
/**
119
* Get file location for reporting purposes
120
* @return FileLocation with file and position information
121
*/
122
FileLocation getReportLocation();
123
124
/**
125
* Get source text content of this node
126
* @return Original source code text for this node
127
*/
128
String getText();
129
}
130
```
131
132
**Usage Examples:**
133
134
```java
135
import net.sourceforge.pmd.lang.ast.Node;
136
import net.sourceforge.pmd.lang.ast.NodeStream;
137
import java.util.List;
138
139
// Basic node navigation
140
public void analyzeNode(Node node) {
141
System.out.printf("Node: %s (Children: %d)%n",
142
node.getXPathNodeName(),
143
node.getNumChildren());
144
145
// Navigate to parent
146
Node parent = node.getParent();
147
if (parent != null) {
148
System.out.printf("Parent: %s%n", parent.getXPathNodeName());
149
}
150
151
// Iterate through children
152
for (int i = 0; i < node.getNumChildren(); i++) {
153
Node child = node.getChild(i);
154
System.out.printf("Child %d: %s%n", i, child.getXPathNodeName());
155
}
156
157
// Or use the children list
158
List<? extends Node> children = node.getChildren();
159
for (Node child : children) {
160
analyzeNode(child); // Recursive analysis
161
}
162
163
// Navigate siblings
164
Node nextSibling = node.getNextSibling();
165
Node prevSibling = node.getPreviousSibling();
166
167
// Get source information
168
TextRegion region = node.getTextRegion();
169
System.out.printf("Text region: %d-%d%n",
170
region.getStartOffset(),
171
region.getEndOffset());
172
173
String sourceText = node.getText();
174
System.out.printf("Source text: %s%n", sourceText.trim());
175
}
176
177
// Finding specific node types (example with hypothetical Java nodes)
178
public void findMethodNodes(Node root) {
179
// Check if any method declarations exist
180
if (root.hasDescendantOfType(ASTMethodDeclaration.class)) {
181
System.out.println("Found method declarations");
182
183
// Find all method declarations
184
List<? extends Node> methods = root.findDescendantsOfType(ASTMethodDeclaration.class);
185
System.out.printf("Found %d methods%n", methods.size());
186
187
for (Node method : methods) {
188
System.out.printf("Method: %s%n", method.getText());
189
}
190
191
// Find first method only
192
Node firstMethod = root.getFirstDescendantOfType(ASTMethodDeclaration.class);
193
if (firstMethod != null) {
194
System.out.printf("First method: %s%n", firstMethod.getText());
195
}
196
}
197
}
198
199
// Using NodeStream for efficient traversal
200
public void streamExample(Node root) {
201
// Stream all descendants
202
root.descendants()
203
.filterIs(ASTVariableDeclarator.class)
204
.forEach(var -> System.out.printf("Variable: %s%n", var.getText()));
205
206
// Stream direct children only
207
root.children()
208
.filter(child -> child.getNumChildren() > 0)
209
.map(Node::getXPathNodeName)
210
.distinct()
211
.forEach(System.out::println);
212
213
// Count specific node types
214
long methodCount = root.descendants()
215
.filterIs(ASTMethodDeclaration.class)
216
.count();
217
218
// Find nodes with specific characteristics
219
List<Node> complexNodes = root.descendants()
220
.filter(node -> node.getNumChildren() > 5)
221
.toList();
222
}
223
```
224
225
### Node Stream Operations
226
227
Efficient streaming API for traversing and filtering AST nodes with functional programming patterns.
228
229
```java { .api }
230
/**
231
* Streaming API for efficient AST traversal and filtering.
232
* Provides functional programming patterns for node processing.
233
*/
234
interface NodeStream<T extends Node> {
235
236
/**
237
* Filter nodes by predicate
238
* @param predicate Function to test each node
239
* @return NodeStream containing only matching nodes
240
*/
241
NodeStream<T> filter(Predicate<? super T> predicate);
242
243
/**
244
* Filter nodes by specific type
245
* @param nodeType Class of target node type
246
* @return NodeStream containing only nodes of specified type
247
*/
248
<R extends Node> NodeStream<R> filterIs(Class<? extends R> nodeType);
249
250
/**
251
* Transform nodes to different type
252
* @param mapper Function to transform each node
253
* @return Stream containing transformed values
254
*/
255
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
256
257
/**
258
* Transform nodes to NodeStream of different type
259
* @param mapper Function to transform each node to NodeStream
260
* @return Flattened NodeStream of transformed nodes
261
*/
262
<R extends Node> NodeStream<R> flatMap(Function<? super T, NodeStream<? extends R>> mapper);
263
264
/**
265
* Get descendants of each node in stream
266
* @return NodeStream containing all descendants
267
*/
268
NodeStream<Node> descendants();
269
270
/**
271
* Get children of each node in stream
272
* @return NodeStream containing all children
273
*/
274
NodeStream<Node> children();
275
276
/**
277
* Execute action for each node
278
* @param action Action to perform on each node
279
*/
280
void forEach(Consumer<? super T> action);
281
282
/**
283
* Collect nodes to list
284
* @return List containing all nodes in stream
285
*/
286
List<T> toList();
287
288
/**
289
* Count nodes in stream
290
* @return Number of nodes in stream
291
*/
292
long count();
293
294
/**
295
* Get first node in stream
296
* @return First node, or empty Optional if stream is empty
297
*/
298
Optional<T> first();
299
300
/**
301
* Get last node in stream
302
* @return Last node, or empty Optional if stream is empty
303
*/
304
Optional<T> last();
305
306
/**
307
* Check if any nodes match predicate
308
* @param predicate Function to test nodes
309
* @return true if at least one node matches
310
*/
311
boolean any(Predicate<? super T> predicate);
312
313
/**
314
* Check if no nodes match predicate
315
* @param predicate Function to test nodes
316
* @return true if no nodes match
317
*/
318
boolean none(Predicate<? super T> predicate);
319
320
/**
321
* Get nodes with distinct values based on key function
322
* @param keyExtractor Function to extract comparison key
323
* @return NodeStream with distinct nodes
324
*/
325
NodeStream<T> distinct(Function<? super T, ?> keyExtractor);
326
327
/**
328
* Limit stream to first N nodes
329
* @param maxSize Maximum number of nodes to include
330
* @return NodeStream limited to specified size
331
*/
332
NodeStream<T> take(long maxSize);
333
334
/**
335
* Skip first N nodes
336
* @param n Number of nodes to skip
337
* @return NodeStream with first N nodes skipped
338
*/
339
NodeStream<T> drop(long n);
340
}
341
```
342
343
**Usage Examples:**
344
345
```java
346
import net.sourceforge.pmd.lang.ast.*;
347
import java.util.List;
348
import java.util.Optional;
349
350
// Complex filtering and transformation examples
351
public class ASTAnalysisExamples {
352
353
public void analyzeComplexity(Node root) {
354
// Find all method declarations with high complexity
355
List<Node> complexMethods = root.descendants()
356
.filterIs(ASTMethodDeclaration.class)
357
.filter(method -> method.descendants()
358
.filterIs(ASTIfStatement.class)
359
.count() > 3)
360
.toList();
361
362
System.out.printf("Found %d complex methods%n", complexMethods.size());
363
}
364
365
public void findUnusedVariables(Node root) {
366
// Find variable declarations
367
NodeStream<ASTVariableDeclarator> variables = root.descendants()
368
.filterIs(ASTVariableDeclarator.class);
369
370
// Check for unused variables (simplified example)
371
variables.filter(var -> {
372
String varName = var.getName();
373
return root.descendants()
374
.filterIs(ASTName.class)
375
.none(name -> name.getImage().equals(varName));
376
}).forEach(unusedVar ->
377
System.out.printf("Unused variable: %s%n", unusedVar.getName())
378
);
379
}
380
381
public void collectStatistics(Node root) {
382
// Count different node types
383
long classCount = root.descendants()
384
.filterIs(ASTClassOrInterfaceDeclaration.class)
385
.count();
386
387
long methodCount = root.descendants()
388
.filterIs(ASTMethodDeclaration.class)
389
.count();
390
391
long lineCount = root.descendants()
392
.map(node -> node.getTextRegion().getEndLine() -
393
node.getTextRegion().getStartLine() + 1)
394
.mapToLong(Integer::longValue)
395
.sum();
396
397
System.out.printf("Classes: %d, Methods: %d, Lines: %d%n",
398
classCount, methodCount, lineCount);
399
}
400
401
public void findDesignPatterns(Node root) {
402
// Find potential singleton pattern usage
403
root.descendants()
404
.filterIs(ASTMethodDeclaration.class)
405
.filter(method -> method.getName().equals("getInstance"))
406
.filter(method -> method.isStatic())
407
.forEach(method -> System.out.printf("Potential singleton: %s%n",
408
method.getParent().getFirstChildOfType(ASTClassOrInterfaceDeclaration.class).getName()));
409
410
// Find builder pattern usage
411
root.descendants()
412
.filterIs(ASTMethodDeclaration.class)
413
.filter(method -> method.getName().startsWith("set") || method.getName().startsWith("with"))
414
.filter(method -> method.getReturnType().equals(method.getDeclaringClass().getName()))
415
.distinct(method -> method.getDeclaringClass())
416
.forEach(builder -> System.out.printf("Potential builder: %s%n",
417
builder.getDeclaringClass().getName()));
418
}
419
420
public void securityAnalysis(Node root) {
421
// Find potential SQL injection points
422
root.descendants()
423
.filterIs(ASTMethodCallExpression.class)
424
.filter(call -> call.getMethodName().contains("executeQuery") ||
425
call.getMethodName().contains("execute"))
426
.filter(call -> call.getArguments().stream()
427
.anyMatch(arg -> arg.hasDescendantOfType(ASTStringLiteral.class)))
428
.forEach(call -> System.out.printf("Potential SQL injection: %s%n",
429
call.getTextRegion()));
430
431
// Find hardcoded passwords/keys
432
root.descendants()
433
.filterIs(ASTStringLiteral.class)
434
.map(Node::getText)
435
.filter(text -> text.toLowerCase().contains("password") ||
436
text.toLowerCase().contains("key") ||
437
text.toLowerCase().contains("secret"))
438
.forEach(suspicious -> System.out.printf("Suspicious string: %s%n", suspicious));
439
}
440
}
441
```
442
443
### AST Visitor Pattern
444
445
Visitor pattern implementation for traversing and processing AST nodes with type-safe method dispatch.
446
447
```java { .api }
448
/**
449
* Visitor pattern for AST traversal with type-safe method dispatch.
450
* Provides structured way to process different node types.
451
*/
452
interface ASTVisitor<T, P> {
453
454
/**
455
* Visit any node (default implementation)
456
* @param node Node to visit
457
* @param data Additional data for processing
458
* @return Result of visiting the node
459
*/
460
T visit(Node node, P data);
461
462
/**
463
* Visit child nodes of given node
464
* @param node Parent node whose children should be visited
465
* @param data Additional data for processing
466
* @return Result of visiting children
467
*/
468
T visitChildren(Node node, P data);
469
}
470
471
/**
472
* Base visitor class with default traversal behavior
473
*/
474
abstract class ASTVisitorBase<T, P> implements ASTVisitor<T, P> {
475
476
@Override
477
public T visit(Node node, P data) {
478
return node.childrenAccept(this, data);
479
}
480
481
@Override
482
public T visitChildren(Node node, P data) {
483
T result = null;
484
for (Node child : node.getChildren()) {
485
T childResult = child.jjtAccept(this, data);
486
if (childResult != null) {
487
result = childResult;
488
}
489
}
490
return result;
491
}
492
}
493
```
494
495
**Usage Examples:**
496
497
```java
498
import net.sourceforge.pmd.lang.ast.*;
499
500
// Example visitor for collecting method information
501
public class MethodCollectorVisitor extends ASTVisitorBase<Void, List<String>> {
502
503
public Void visit(ASTMethodDeclaration node, List<String> methods) {
504
// Collect method information
505
String methodInfo = String.format("%s (line %d)",
506
node.getName(),
507
node.getTextRegion().getStartLine());
508
methods.add(methodInfo);
509
510
// Continue visiting children
511
return visitChildren(node, methods);
512
}
513
514
public Void visit(ASTClassOrInterfaceDeclaration node, List<String> methods) {
515
System.out.printf("Analyzing class: %s%n", node.getName());
516
return visitChildren(node, methods);
517
}
518
}
519
520
// Usage example
521
public void collectMethods(Node root) {
522
List<String> methods = new ArrayList<>();
523
MethodCollectorVisitor visitor = new MethodCollectorVisitor();
524
root.jjtAccept(visitor, methods);
525
526
System.out.printf("Found %d methods:%n", methods.size());
527
methods.forEach(System.out::println);
528
}
529
530
// Visitor for complexity calculation
531
public class ComplexityVisitor extends ASTVisitorBase<Integer, Void> {
532
533
public Integer visit(ASTMethodDeclaration node, Void data) {
534
int complexity = 1; // Base complexity
535
536
// Add complexity for control flow nodes
537
complexity += node.descendants()
538
.filterIs(ASTIfStatement.class)
539
.count();
540
541
complexity += node.descendants()
542
.filterIs(ASTWhileStatement.class)
543
.count();
544
545
complexity += node.descendants()
546
.filterIs(ASTForStatement.class)
547
.count();
548
549
complexity += node.descendants()
550
.filterIs(ASTSwitchStatement.class)
551
.children()
552
.filterIs(ASTSwitchLabel.class)
553
.count();
554
555
System.out.printf("Method %s complexity: %d%n", node.getName(), complexity);
556
return complexity;
557
}
558
}
559
```
560
561
## Types
562
563
```java { .api }
564
/**
565
* Text region representing position in source file
566
*/
567
interface TextRegion {
568
569
/**
570
* Get start offset in characters
571
* @return Zero-based start position
572
*/
573
int getStartOffset();
574
575
/**
576
* Get end offset in characters
577
* @return Zero-based end position (exclusive)
578
*/
579
int getEndOffset();
580
581
/**
582
* Get start line number
583
* @return One-based start line
584
*/
585
int getStartLine();
586
587
/**
588
* Get end line number
589
* @return One-based end line
590
*/
591
int getEndLine();
592
593
/**
594
* Get start column number
595
* @return One-based start column
596
*/
597
int getStartColumn();
598
599
/**
600
* Get end column number
601
* @return One-based end column
602
*/
603
int getEndColumn();
604
605
/**
606
* Get length in characters
607
* @return Number of characters in region
608
*/
609
int getLength();
610
611
/**
612
* Check if region contains given offset
613
* @param offset Character offset to check
614
* @return true if offset is within region
615
*/
616
boolean contains(int offset);
617
}
618
619
/**
620
* File location for reporting and error messages
621
*/
622
interface FileLocation {
623
624
/**
625
* Get file identifier
626
* @return FileId for the source file
627
*/
628
FileId getFileId();
629
630
/**
631
* Get text region within file
632
* @return TextRegion with position information
633
*/
634
TextRegion getTextRegion();
635
636
/**
637
* Get display string for location
638
* @return Human-readable location description
639
*/
640
String getDisplayName();
641
}
642
643
/**
644
* Text document interface for source content
645
*/
646
interface TextDocument {
647
648
/**
649
* Get complete document text
650
* @return Full source text content
651
*/
652
String getText();
653
654
/**
655
* Get text for specific region
656
* @param region TextRegion to extract
657
* @return Text content for the region
658
*/
659
String getText(TextRegion region);
660
661
/**
662
* Get line content by number
663
* @param lineNumber One-based line number
664
* @return Text content of the line
665
*/
666
String getLine(int lineNumber);
667
668
/**
669
* Get total number of lines
670
* @return Line count in document
671
*/
672
int getLineCount();
673
674
/**
675
* Get file identifier
676
* @return FileId for this document
677
*/
678
FileId getFileId();
679
}
680
681
/**
682
* XPath query capabilities for AST nodes
683
*/
684
interface XPathCapable {
685
686
/**
687
* Execute XPath query on node
688
* @param xpath XPath expression to evaluate
689
* @return List of matching nodes
690
*/
691
List<Node> findChildNodesWithXPath(String xpath);
692
693
/**
694
* Check if XPath query matches any nodes
695
* @param xpath XPath expression to test
696
* @return true if query matches at least one node
697
*/
698
boolean hasDescendantMatchingXPath(String xpath);
699
}
700
701
/**
702
* Abstract base node class providing common functionality
703
*/
704
abstract class AbstractNode implements Node {
705
706
/**
707
* Accept visitor with double dispatch
708
* @param visitor ASTVisitor to accept
709
* @param data Additional data for visitor
710
* @return Result from visitor
711
*/
712
public abstract <T, P> T jjtAccept(ASTVisitor<T, P> visitor, P data);
713
714
/**
715
* Have children accept visitor
716
* @param visitor ASTVisitor for children
717
* @param data Additional data for visitor
718
* @return Result from visiting children
719
*/
720
public <T, P> T childrenAccept(ASTVisitor<T, P> visitor, P data) {
721
T result = null;
722
for (Node child : getChildren()) {
723
T childResult = child.jjtAccept(visitor, data);
724
if (childResult != null) {
725
result = childResult;
726
}
727
}
728
return result;
729
}
730
}
731
```