0
# Visitor Pattern
1
2
Type-safe visitor pattern implementation for traversing Scala ASTs, enabling rule development and custom analysis through comprehensive visit methods for all node types. The visitor pattern provides the primary mechanism for analyzing Scala code in PMD.
3
4
## Capabilities
5
6
### Visitor Interface
7
8
The main visitor interface defining methods for visiting every type of Scala AST node.
9
10
```java { .api }
11
/**
12
* A Visitor Pattern Interface for the Scala AST
13
* @param <D> The type of the data input to each visit method
14
* @param <R> the type of the returned data from each visit method
15
*/
16
public interface ScalaParserVisitor<D, R> {
17
/**
18
* Visit an arbitrary Scala Node (any node in the tree)
19
* @param node the node of the tree
20
* @param data context-specific data
21
* @return context-specific data
22
*/
23
R visit(ScalaNode<?> node, D data);
24
25
/**
26
* Visit the Source Node (the root node of the tree)
27
* @param node the root node of the tree
28
* @param data context-specific data
29
* @return context-specific data
30
*/
31
R visit(ASTSource node, D data);
32
33
// Case and Constructor nodes
34
R visit(ASTCase node, D data);
35
R visit(ASTCtorPrimary node, D data);
36
R visit(ASTCtorSecondary node, D data);
37
38
// Declaration nodes
39
R visit(ASTDeclDef node, D data);
40
R visit(ASTDeclType node, D data);
41
R visit(ASTDeclVal node, D data);
42
R visit(ASTDeclVar node, D data);
43
44
// Definition nodes
45
R visit(ASTDefnClass node, D data);
46
R visit(ASTDefnDef node, D data);
47
R visit(ASTDefnMacro node, D data);
48
R visit(ASTDefnObject node, D data);
49
R visit(ASTDefnTrait node, D data);
50
R visit(ASTDefnType node, D data);
51
R visit(ASTDefnVal node, D data);
52
R visit(ASTDefnVar node, D data);
53
54
// Term expression nodes
55
R visit(ASTTermApply node, D data);
56
R visit(ASTTermApplyInfix node, D data);
57
R visit(ASTTermApplyType node, D data);
58
R visit(ASTTermApplyUnary node, D data);
59
R visit(ASTTermAssign node, D data);
60
R visit(ASTTermBlock node, D data);
61
R visit(ASTTermFor node, D data);
62
R visit(ASTTermForYield node, D data);
63
R visit(ASTTermFunction node, D data);
64
R visit(ASTTermIf node, D data);
65
R visit(ASTTermMatch node, D data);
66
R visit(ASTTermName node, D data);
67
R visit(ASTTermNew node, D data);
68
R visit(ASTTermNewAnonymous node, D data);
69
R visit(ASTTermSelect node, D data);
70
R visit(ASTTermThis node, D data);
71
R visit(ASTTermTry node, D data);
72
R visit(ASTTermWhile node, D data);
73
74
// Type system nodes
75
R visit(ASTTypeApply node, D data);
76
R visit(ASTTypeFunction node, D data);
77
R visit(ASTTypeName node, D data);
78
R visit(ASTTypeParam node, D data);
79
R visit(ASTTypeTuple node, D data);
80
R visit(ASTTypeWith node, D data);
81
82
// Pattern matching nodes
83
R visit(ASTPatAlternative node, D data);
84
R visit(ASTPatBind node, D data);
85
R visit(ASTPatExtract node, D data);
86
R visit(ASTPatTuple node, D data);
87
R visit(ASTPatVar node, D data);
88
R visit(ASTPatWildcard node, D data);
89
90
// Literal nodes
91
R visit(ASTLitBoolean node, D data);
92
R visit(ASTLitInt node, D data);
93
R visit(ASTLitLong node, D data);
94
R visit(ASTLitDouble node, D data);
95
R visit(ASTLitFloat node, D data);
96
R visit(ASTLitString node, D data);
97
R visit(ASTLitChar node, D data);
98
R visit(ASTLitNull node, D data);
99
R visit(ASTLitUnit node, D data);
100
101
// Modifier nodes
102
R visit(ASTModAbstract node, D data);
103
R visit(ASTModCase node, D data);
104
R visit(ASTModFinal node, D data);
105
R visit(ASTModImplicit node, D data);
106
R visit(ASTModLazy node, D data);
107
R visit(ASTModOverride node, D data);
108
R visit(ASTModPrivate node, D data);
109
R visit(ASTModProtected node, D data);
110
R visit(ASTModSealed node, D data);
111
112
// Import system nodes
113
R visit(ASTImport node, D data);
114
R visit(ASTImporter node, D data);
115
R visit(ASTImporteeName node, D data);
116
R visit(ASTImporteeRename node, D data);
117
R visit(ASTImporteeWildcard node, D data);
118
R visit(ASTImporteeUnimport node, D data);
119
120
// And many more visit methods for all other AST node types...
121
}
122
```
123
124
### Visitor Adapter
125
126
Default implementation providing convenient base for custom visitors.
127
128
```java { .api }
129
/**
130
* Default implementation of ScalaParserVisitor providing no-op visit methods
131
* Extend this class and override specific visit methods as needed
132
*/
133
public class ScalaParserVisitorAdapter implements ScalaParserVisitor<Object, Object> {
134
/**
135
* Default visit method that can be overridden for generic node handling
136
* @param node any Scala AST node
137
* @param data context data
138
* @return context data (default implementation returns data unchanged)
139
*/
140
@Override
141
public Object visit(ScalaNode<?> node, Object data) {
142
// Default: visit all children
143
for (ScalaNode<?> child : node.children()) {
144
child.accept(this, data);
145
}
146
return data;
147
}
148
149
/**
150
* Visit root source node - delegates to generic visit method by default
151
* @param node the source root node
152
* @param data context data
153
* @return result of generic visit method
154
*/
155
@Override
156
public Object visit(ASTSource node, Object data) {
157
return visit((ScalaNode<?>) node, data);
158
}
159
160
// All other visit methods delegate to the generic visit method by default
161
// Override specific methods to customize behavior for particular node types
162
163
@Override
164
public Object visit(ASTDefnClass node, Object data) {
165
return visit((ScalaNode<?>) node, data);
166
}
167
168
@Override
169
public Object visit(ASTDefnDef node, Object data) {
170
return visit((ScalaNode<?>) node, data);
171
}
172
173
@Override
174
public Object visit(ASTTermApply node, Object data) {
175
return visit((ScalaNode<?>) node, data);
176
}
177
178
// ... similar delegating implementations for all other node types
179
}
180
```
181
182
**Usage Examples:**
183
184
```java
185
// Create custom visitor by extending adapter
186
public class ScalaMethodVisitor extends ScalaParserVisitorAdapter {
187
private List<String> methodNames = new ArrayList<>();
188
189
@Override
190
public Object visit(ASTDefnDef node, Object data) {
191
// Custom logic for method definitions
192
System.out.println("Found method definition");
193
194
// Continue traversing children
195
return super.visit(node, data);
196
}
197
198
@Override
199
public Object visit(ASTTermName node, Object data) {
200
// Extract method names
201
String name = extractName(node);
202
methodNames.add(name);
203
return super.visit(node, data);
204
}
205
206
public List<String> getMethodNames() {
207
return methodNames;
208
}
209
}
210
211
// Use the visitor
212
ASTSource ast = parser.parse("Example.scala", sourceReader);
213
ScalaMethodVisitor visitor = new ScalaMethodVisitor();
214
ast.accept(visitor, null);
215
List<String> methods = visitor.getMethodNames();
216
```
217
218
### Generic Type-Safe Visitor
219
220
Template for creating strongly typed visitors with custom data and return types.
221
222
```java { .api }
223
/**
224
* Example of a type-safe visitor with custom data and return types
225
*/
226
public class CustomAnalysisVisitor implements ScalaParserVisitor<AnalysisContext, AnalysisResult> {
227
@Override
228
public AnalysisResult visit(ScalaNode<?> node, AnalysisContext data) {
229
// Generic node processing
230
AnalysisResult result = new AnalysisResult();
231
232
// Visit all children and combine results
233
for (ScalaNode<?> child : node.children()) {
234
AnalysisResult childResult = child.accept(this, data);
235
result.merge(childResult);
236
}
237
238
return result;
239
}
240
241
@Override
242
public AnalysisResult visit(ASTSource node, AnalysisContext data) {
243
// Root node specific logic
244
data.setSourceFile(node);
245
return visit((ScalaNode<?>) node, data);
246
}
247
248
@Override
249
public AnalysisResult visit(ASTDefnClass node, AnalysisContext data) {
250
// Class-specific analysis
251
AnalysisResult result = new AnalysisResult();
252
result.addClass(extractClassName(node));
253
254
// Analyze class body
255
AnalysisResult bodyResult = visit((ScalaNode<?>) node, data);
256
result.merge(bodyResult);
257
258
return result;
259
}
260
261
@Override
262
public AnalysisResult visit(ASTDefnDef node, AnalysisContext data) {
263
// Method-specific analysis
264
AnalysisResult result = new AnalysisResult();
265
result.addMethod(extractMethodInfo(node));
266
267
return result;
268
}
269
270
// Implement other visit methods as needed...
271
}
272
273
// Supporting classes
274
class AnalysisContext {
275
private ASTSource sourceFile;
276
private String currentClass;
277
278
public void setSourceFile(ASTSource source) { this.sourceFile = source; }
279
public ASTSource getSourceFile() { return sourceFile; }
280
// ... other context methods
281
}
282
283
class AnalysisResult {
284
private List<String> classes = new ArrayList<>();
285
private List<String> methods = new ArrayList<>();
286
287
public void addClass(String className) { classes.add(className); }
288
public void addMethod(String methodName) { methods.add(methodName); }
289
public void merge(AnalysisResult other) {
290
classes.addAll(other.classes);
291
methods.addAll(other.methods);
292
}
293
// ... other result methods
294
}
295
```
296
297
### Visitor Usage Patterns
298
299
Common patterns for using visitors effectively in Scala AST analysis.
300
301
**Pattern 1: Collecting Information**
302
303
```java
304
public class InfoCollector extends ScalaParserVisitorAdapter {
305
private final Map<String, Integer> nodeCounts = new HashMap<>();
306
307
@Override
308
public Object visit(ScalaNode<?> node, Object data) {
309
// Count node types
310
String nodeType = node.getClass().getSimpleName();
311
nodeCounts.merge(nodeType, 1, Integer::sum);
312
313
return super.visit(node, data);
314
}
315
316
public Map<String, Integer> getNodeCounts() {
317
return nodeCounts;
318
}
319
}
320
```
321
322
**Pattern 2: Conditional Processing**
323
324
```java
325
public class ConditionalVisitor extends ScalaParserVisitorAdapter {
326
private final Predicate<ScalaNode<?>> condition;
327
private final Consumer<ScalaNode<?>> processor;
328
329
public ConditionalVisitor(Predicate<ScalaNode<?>> condition, Consumer<ScalaNode<?>> processor) {
330
this.condition = condition;
331
this.processor = processor;
332
}
333
334
@Override
335
public Object visit(ScalaNode<?> node, Object data) {
336
if (condition.test(node)) {
337
processor.accept(node);
338
}
339
return super.visit(node, data);
340
}
341
}
342
343
// Usage
344
ConditionalVisitor visitor = new ConditionalVisitor(
345
node -> node instanceof ASTDefnDef,
346
node -> System.out.println("Processing method: " + node)
347
);
348
```
349
350
**Pattern 3: Early Termination**
351
352
```java
353
public class SearchVisitor extends ScalaParserVisitorAdapter {
354
private final Predicate<ScalaNode<?>> searchCondition;
355
private ScalaNode<?> foundNode;
356
357
public SearchVisitor(Predicate<ScalaNode<?>> searchCondition) {
358
this.searchCondition = searchCondition;
359
}
360
361
@Override
362
public Object visit(ScalaNode<?> node, Object data) {
363
if (foundNode != null) {
364
return data; // Early termination - already found
365
}
366
367
if (searchCondition.test(node)) {
368
foundNode = node;
369
return data; // Found - stop searching
370
}
371
372
return super.visit(node, data);
373
}
374
375
public Optional<ScalaNode<?>> getFoundNode() {
376
return Optional.ofNullable(foundNode);
377
}
378
}
379
```
380
381
**Pattern 4: Context-Aware Traversal**
382
383
```java
384
public class ContextAwareVisitor extends ScalaParserVisitorAdapter {
385
private final Stack<ScalaNode<?>> context = new Stack<>();
386
387
@Override
388
public Object visit(ScalaNode<?> node, Object data) {
389
context.push(node);
390
try {
391
// Process with full context available
392
processWithContext(node, context);
393
return super.visit(node, data);
394
} finally {
395
context.pop();
396
}
397
}
398
399
private void processWithContext(ScalaNode<?> current, Stack<ScalaNode<?>> context) {
400
// Access parent nodes via context stack
401
if (current instanceof ASTTermName && !context.isEmpty()) {
402
ScalaNode<?> parent = context.get(context.size() - 2); // Skip current
403
if (parent instanceof ASTDefnDef) {
404
System.out.println("Term name inside method definition");
405
}
406
}
407
}
408
}
409
```
410
411
### Integration with PMD Rules
412
413
The visitor pattern integrates directly with PMD's rule system through the ScalaRule base class:
414
415
```java
416
// ScalaRule automatically implements ScalaParserVisitor
417
public class CustomScalaRule extends ScalaRule {
418
@Override
419
public Object visit(ASTDefnDef node, Object data) {
420
RuleContext ctx = (RuleContext) data;
421
422
// Rule logic here
423
if (violatesRule(node)) {
424
addViolation(ctx, node, "Rule violation message");
425
}
426
427
return super.visit(node, data);
428
}
429
430
private boolean violatesRule(ASTDefnDef method) {
431
// Custom rule logic
432
return true;
433
}
434
}
435
```