0
# Rule Development Framework
1
2
Base classes and utilities for creating custom static analysis rules for Velocity templates. The rule framework provides visitor pattern integration, violation reporting, and statistical analysis capabilities for developing both simple and complex rules.
3
4
## Capabilities
5
6
### Base Rule Class
7
8
Abstract base class that all Velocity rules must extend, providing visitor pattern integration and violation reporting.
9
10
```java { .api }
11
/**
12
* Abstract base class for all Velocity template rules.
13
* Integrates with PMD's rule framework and provides visitor pattern support
14
* for analyzing Velocity AST nodes.
15
*/
16
public abstract class AbstractVmRule extends AbstractRule
17
implements VmParserVisitor, ImmutableLanguage {
18
19
/**
20
* Main entry point for rule execution.
21
* Called by PMD framework with list of AST nodes to analyze.
22
* @param nodes List of root nodes to analyze (typically one per template)
23
* @param ctx Rule context containing configuration and violation reporting
24
*/
25
public void apply(List<? extends Node> nodes, RuleContext ctx);
26
27
/**
28
* Helper method to visit all nodes in a list with proper context.
29
* Useful for processing multiple root nodes or child node collections.
30
* @param nodes List of Node instances to visit (filtered to VmNode internally)
31
* @param ctx Rule context for violation reporting
32
*/
33
protected void visitAll(List<? extends Node> nodes, RuleContext ctx);
34
35
// All VmParserVisitor methods are available for override:
36
37
/**
38
* Visit any VmNode. Default implementation traverses children.
39
* Override to provide custom handling for specific node types.
40
* @param node The AST node being visited
41
* @param data Rule context passed during traversal
42
* @return Typically null, or custom data for specialized rules
43
*/
44
public Object visit(VmNode node, Object data);
45
46
/**
47
* Visit variable reference nodes ($variable, $object.property).
48
* Override to analyze variable usage patterns.
49
* @param node Reference AST node
50
* @param data Rule context
51
* @return Typically null
52
*/
53
public Object visit(ASTReference node, Object data);
54
55
/**
56
* Visit directive nodes (#if, #foreach, #set, etc.).
57
* Override to analyze directive usage and structure.
58
* @param node Directive AST node
59
* @param data Rule context
60
* @return Typically null
61
*/
62
public Object visit(ASTDirective node, Object data);
63
64
/**
65
* Visit method call nodes.
66
* Override to analyze method invocation patterns.
67
* @param node Method call AST node
68
* @param data Rule context
69
* @return Typically null
70
*/
71
public Object visit(ASTMethod node, Object data);
72
73
// Mathematical operation visitor methods
74
public Object visit(ASTAddNode node, Object data);
75
public Object visit(ASTSubtractNode node, Object data);
76
public Object visit(ASTMulNode node, Object data);
77
public Object visit(ASTDivNode node, Object data);
78
public Object visit(ASTModNode node, Object data);
79
public Object visit(ASTMathNode node, Object data);
80
81
// Logical operation visitor methods
82
public Object visit(ASTOrNode node, Object data);
83
public Object visit(ASTAndNode node, Object data);
84
public Object visit(ASTNotNode node, Object data);
85
86
// Comparison operation visitor methods
87
public Object visit(ASTEQNode node, Object data);
88
public Object visit(ASTNENode node, Object data);
89
public Object visit(ASTLTNode node, Object data);
90
public Object visit(ASTGTNode node, Object data);
91
public Object visit(ASTLENode node, Object data);
92
public Object visit(ASTGENode node, Object data);
93
94
// Control flow visitor methods
95
public Object visit(ASTIfStatement node, Object data);
96
public Object visit(ASTElseStatement node, Object data);
97
public Object visit(ASTElseIfStatement node, Object data);
98
public Object visit(ASTForeachStatement node, Object data);
99
public Object visit(ASTSetDirective node, Object data);
100
public Object visit(ASTBlock node, Object data);
101
102
// Expression and structure visitor methods
103
public Object visit(ASTExpression node, Object data);
104
public Object visit(ASTAssignment node, Object data);
105
public Object visit(ASTMap node, Object data);
106
public Object visit(ASTObjectArray node, Object data);
107
public Object visit(ASTIntegerRange node, Object data);
108
public Object visit(ASTIndex node, Object data);
109
110
// Content visitor methods
111
public Object visit(ASTText node, Object data);
112
public Object visit(ASTTextblock node, Object data);
113
public Object visit(ASTComment node, Object data);
114
115
// Literal visitor methods
116
public Object visit(ASTFloatingPointLiteral node, Object data);
117
public Object visit(ASTIntegerLiteral node, Object data);
118
public Object visit(ASTStringLiteral node, Object data);
119
public Object visit(ASTTrue node, Object data);
120
public Object visit(ASTFalse node, Object data);
121
122
// Identifier visitor methods
123
public Object visit(ASTIdentifier node, Object data);
124
public Object visit(ASTWord node, Object data);
125
126
// Special node visitor methods
127
public Object visit(ASTEscape node, Object data);
128
public Object visit(ASTEscapedDirective node, Object data);
129
public Object visit(ASTprocess node, Object data);
130
}
131
```
132
133
### Statistical Rule Base Class
134
135
Specialized base class for rules that perform statistical analysis (complexity, length, nesting depth, etc.).
136
137
```java { .api }
138
/**
139
* Abstract base class for statistical rules that measure quantitative
140
* properties of Velocity templates (length, complexity, nesting depth, etc.).
141
* Extends AbstractVmRule with statistical analysis capabilities.
142
*/
143
public abstract class AbstractStatisticalVmRule extends AbstractVmRule {
144
145
// Statistical analysis framework
146
// Subclasses implement specific metrics and thresholds
147
// Automatic threshold checking and violation reporting
148
149
/**
150
* Override to define the statistical metric being measured.
151
* Called by framework to collect measurement data.
152
* @param node AST node to measure
153
* @param data Context data
154
* @return Numeric measurement value
155
*/
156
protected abstract double getMeasurement(VmNode node, Object data);
157
158
/**
159
* Override to define the threshold for violations.
160
* Values exceeding this threshold trigger rule violations.
161
* @return Maximum acceptable value for the metric
162
*/
163
protected abstract double getThreshold();
164
165
/**
166
* Override to provide custom violation messages.
167
* @param measurement Actual measured value
168
* @param threshold Maximum acceptable value
169
* @return Descriptive violation message
170
*/
171
protected String getViolationMessage(double measurement, double threshold);
172
}
173
```
174
175
## Rule Implementation Examples
176
177
### Simple Reference Analysis Rule
178
179
```java
180
import net.sourceforge.pmd.lang.vm.rule.AbstractVmRule;
181
import net.sourceforge.pmd.lang.vm.ast.ASTReference;
182
183
/**
184
* Example rule that flags references with problematic naming patterns.
185
*/
186
public class ProblematicReferenceRule extends AbstractVmRule {
187
188
@Override
189
public Object visit(ASTReference node, Object data) {
190
String refName = node.getRootString();
191
192
if (refName != null && isProblematic(refName)) {
193
addViolation(data, node,
194
"Reference name '" + refName + "' follows problematic pattern");
195
}
196
197
return super.visit(node, data);
198
}
199
200
private boolean isProblematic(String refName) {
201
// Example: flag references starting with underscore
202
return refName.startsWith("_") || refName.length() > 50;
203
}
204
}
205
```
206
207
### Complex Control Flow Rule
208
209
```java
210
import net.sourceforge.pmd.lang.vm.rule.AbstractVmRule;
211
import net.sourceforge.pmd.lang.vm.ast.ASTIfStatement;
212
import net.sourceforge.pmd.lang.vm.ast.ASTForeachStatement;
213
import java.util.Stack;
214
215
/**
216
* Example rule that detects overly complex nested control structures.
217
*/
218
public class ComplexControlFlowRule extends AbstractVmRule {
219
220
private Stack<String> controlStack = new Stack<>();
221
private static final int MAX_NESTING = 3;
222
223
@Override
224
public Object visit(ASTIfStatement node, Object data) {
225
controlStack.push("if");
226
227
if (controlStack.size() > MAX_NESTING) {
228
addViolation(data, node,
229
"Control flow nesting exceeds maximum depth of " + MAX_NESTING);
230
}
231
232
Object result = super.visit(node, data);
233
controlStack.pop();
234
return result;
235
}
236
237
@Override
238
public Object visit(ASTForeachStatement node, Object data) {
239
controlStack.push("foreach");
240
241
if (controlStack.size() > MAX_NESTING) {
242
addViolation(data, node,
243
"Control flow nesting exceeds maximum depth of " + MAX_NESTING);
244
}
245
246
Object result = super.visit(node, data);
247
controlStack.pop();
248
return result;
249
}
250
}
251
```
252
253
### Statistical Rule Example
254
255
```java
256
import net.sourceforge.pmd.lang.vm.rule.AbstractStatisticalVmRule;
257
import net.sourceforge.pmd.lang.vm.ast.VmNode;
258
import net.sourceforge.pmd.lang.vm.ast.ASTReference;
259
260
/**
261
* Example statistical rule that measures reference density in templates.
262
*/
263
public class ReferenceDensityRule extends AbstractStatisticalVmRule {
264
265
@Override
266
protected double getMeasurement(VmNode node, Object data) {
267
ReferenceCounter counter = new ReferenceCounter();
268
node.jjtAccept(counter, null);
269
270
int totalNodes = counter.getTotalNodes();
271
int referenceNodes = counter.getReferenceCount();
272
273
return totalNodes > 0 ? (double) referenceNodes / totalNodes : 0.0;
274
}
275
276
@Override
277
protected double getThreshold() {
278
return 0.5; // 50% reference density threshold
279
}
280
281
@Override
282
protected String getViolationMessage(double measurement, double threshold) {
283
return String.format("Reference density %.2f exceeds threshold %.2f",
284
measurement, threshold);
285
}
286
287
private static class ReferenceCounter extends VmParserVisitorAdapter {
288
private int totalNodes = 0;
289
private int referenceCount = 0;
290
291
@Override
292
public Object visit(VmNode node, Object data) {
293
totalNodes++;
294
return super.visit(node, data);
295
}
296
297
@Override
298
public Object visit(ASTReference node, Object data) {
299
referenceCount++;
300
return super.visit(node, data);
301
}
302
303
public int getTotalNodes() { return totalNodes; }
304
public int getReferenceCount() { return referenceCount; }
305
}
306
}
307
```
308
309
### Multi-Visit Pattern Rule
310
311
```java
312
import net.sourceforge.pmd.lang.vm.rule.AbstractVmRule;
313
import net.sourceforge.pmd.lang.vm.ast.ASTReference;
314
import net.sourceforge.pmd.lang.vm.ast.ASTSetDirective;
315
import java.util.Set;
316
import java.util.HashSet;
317
318
/**
319
* Example rule that tracks variable assignments and usage across the template.
320
*/
321
public class UnusedVariableRule extends AbstractVmRule {
322
323
private Set<String> definedVars = new HashSet<>();
324
private Set<String> usedVars = new HashSet<>();
325
326
@Override
327
public void apply(List<? extends Node> nodes, RuleContext ctx) {
328
// Reset state for each template
329
definedVars.clear();
330
usedVars.clear();
331
332
// First pass: collect definitions and usage
333
super.apply(nodes, ctx);
334
335
// Second pass: report unused variables
336
Set<String> unusedVars = new HashSet<>(definedVars);
337
unusedVars.removeAll(usedVars);
338
339
for (String unused : unusedVars) {
340
// Find the node where variable was defined for violation reporting
341
// This requires additional tracking during first pass
342
reportUnusedVariable(unused, ctx);
343
}
344
}
345
346
@Override
347
public Object visit(ASTSetDirective node, Object data) {
348
// Track variable definitions
349
String varName = extractVariableName(node);
350
if (varName != null) {
351
definedVars.add(varName);
352
}
353
return super.visit(node, data);
354
}
355
356
@Override
357
public Object visit(ASTReference node, Object data) {
358
// Track variable usage
359
String refName = node.getRootString();
360
if (refName != null) {
361
usedVars.add(refName);
362
}
363
return super.visit(node, data);
364
}
365
366
private String extractVariableName(ASTSetDirective node) {
367
// Implementation depends on AST structure
368
// Extract variable name from #set directive
369
return null; // Simplified for example
370
}
371
372
private void reportUnusedVariable(String varName, RuleContext ctx) {
373
// Implementation requires tracking definition locations
374
// Simplified for example
375
}
376
}
377
```
378
379
## Rule Configuration and Properties
380
381
### Property-Based Configuration
382
383
```java
384
import net.sourceforge.pmd.properties.PropertyDescriptor;
385
import net.sourceforge.pmd.properties.PropertyFactory;
386
387
public class ConfigurableRule extends AbstractVmRule {
388
389
private static final PropertyDescriptor<Integer> MAX_LENGTH_PROP =
390
PropertyFactory.intProperty("maxLength")
391
.desc("Maximum allowed length")
392
.defaultValue(100)
393
.range(1, 1000)
394
.build();
395
396
private static final PropertyDescriptor<String> PATTERN_PROP =
397
PropertyFactory.stringProperty("pattern")
398
.desc("Regular expression pattern to match")
399
.defaultValue(".*")
400
.build();
401
402
public ConfigurableRule() {
403
definePropertyDescriptor(MAX_LENGTH_PROP);
404
definePropertyDescriptor(PATTERN_PROP);
405
}
406
407
@Override
408
public Object visit(ASTReference node, Object data) {
409
int maxLength = getProperty(MAX_LENGTH_PROP);
410
String pattern = getProperty(PATTERN_PROP);
411
412
String refName = node.getRootString();
413
if (refName != null) {
414
if (refName.length() > maxLength) {
415
addViolation(data, node, "Reference too long: " + refName.length());
416
}
417
418
if (!refName.matches(pattern)) {
419
addViolation(data, node, "Reference doesn't match pattern: " + refName);
420
}
421
}
422
423
return super.visit(node, data);
424
}
425
}
426
```
427
428
## Best Practices
429
430
### Rule Performance
431
432
```java
433
public class EfficientRule extends AbstractVmRule {
434
435
// Pre-compile patterns and expensive objects
436
private static final Pattern PROBLEM_PATTERN = Pattern.compile("^temp_.*");
437
438
// Use early returns to avoid unnecessary processing
439
@Override
440
public Object visit(ASTReference node, Object data) {
441
String refName = node.getRootString();
442
if (refName == null) {
443
return super.visit(node, data); // Early return
444
}
445
446
if (PROBLEM_PATTERN.matcher(refName).matches()) {
447
addViolation(data, node, "Problematic reference pattern");
448
}
449
450
return super.visit(node, data);
451
}
452
}
453
```
454
455
### Violation Reporting
456
457
```java
458
public class DetailedViolationRule extends AbstractVmRule {
459
460
@Override
461
public Object visit(ASTReference node, Object data) {
462
String refName = node.getRootString();
463
464
if (shouldReport(refName)) {
465
// Provide detailed context in violation message
466
String message = String.format(
467
"Reference '%s' at %s:%d violates naming convention. " +
468
"Expected pattern: camelCase starting with lowercase letter.",
469
refName, node.getTemplateName(), node.getLine()
470
);
471
472
addViolation(data, node, message);
473
}
474
475
return super.visit(node, data);
476
}
477
478
private boolean shouldReport(String refName) {
479
return refName != null &&
480
!refName.matches("^[a-z][a-zA-Z0-9]*$");
481
}
482
}
483
```