0
# Rule Development
1
2
PMD JSP provides a framework for creating custom JSP analysis rules. Rules implement the visitor pattern to analyze JSP AST and report code quality violations.
3
4
## Rule Base Class
5
6
### AbstractJspRule
7
8
Base class for all JSP rules, implementing both PMD's rule interface and JSP visitor pattern.
9
10
```java { .api }
11
public abstract class AbstractJspRule extends AbstractRule implements JspVisitor<Object, Object> {
12
public void apply(Node target, RuleContext ctx);
13
public Object visitNode(Node node, Object param);
14
}
15
```
16
17
**Usage:**
18
19
```java
20
import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule;
21
import net.sourceforge.pmd.lang.jsp.ast.*;
22
import net.sourceforge.pmd.reporting.RuleContext;
23
24
public class MyJspRule extends AbstractJspRule {
25
26
@Override
27
public Object visit(ASTElExpression node, Object data) {
28
// Rule implementation
29
if (violatesRule(node)) {
30
((RuleContext) data).addViolation(node, "Violation message");
31
}
32
return super.visit(node, data);
33
}
34
35
private boolean violatesRule(ASTElExpression node) {
36
// Custom rule logic
37
return node.getContent().contains("dangerousFunction");
38
}
39
}
40
```
41
42
**Methods:**
43
44
- `apply(Node, RuleContext)`: Entry point called by PMD framework
45
- `visitNode(Node, Object)`: Default visitor implementation that traverses all children
46
47
**Note:** The `data` parameter in visit methods is the RuleContext passed from the PMD framework and should be cast to RuleContext when reporting violations.
48
49
## Rule Implementation Patterns
50
51
### Simple Violation Detection
52
53
```java
54
import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule;
55
import net.sourceforge.pmd.lang.jsp.ast.ASTJspScriptlet;
56
import net.sourceforge.pmd.reporting.RuleContext;
57
58
public class NoScriptletsRule extends AbstractJspRule {
59
60
@Override
61
public Object visit(ASTJspScriptlet node, Object data) {
62
// Report violation for any scriptlet
63
((RuleContext) data).addViolation(node,
64
"Avoid using JSP scriptlets. Use JSTL or EL expressions instead.");
65
66
return super.visit(node, data);
67
}
68
}
69
```
70
71
### Conditional Rules
72
73
```java
74
import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule;
75
import net.sourceforge.pmd.lang.jsp.ast.*;
76
import net.sourceforge.pmd.reporting.RuleContext;
77
78
public class NoInlineStyleRule extends AbstractJspRule {
79
80
@Override
81
public Object visit(ASTElement node, Object data) {
82
// Check for style attributes
83
node.children(ASTAttribute.class)
84
.filter(attr -> "style".equals(attr.getName()))
85
.forEach(attr -> {
86
((RuleContext) data).addViolation(attr,
87
"Avoid inline styles. Use CSS classes instead.");
88
});
89
90
return super.visit(node, data);
91
}
92
}
93
```
94
95
### Content Analysis Rules
96
97
```java
98
import net.sourceforge.pmd.lang.jsp.rule.AbstractJspRule;
99
import net.sourceforge.pmd.lang.jsp.ast.ASTElExpression;
100
import net.sourceforge.pmd.reporting.RuleContext;
101
102
public class UnsanitizedExpressionRule extends AbstractJspRule {
103
104
@Override
105
public Object visit(ASTElExpression node, Object data) {
106
String content = node.getContent();
107
108
// Check if expression is in a taglib context
109
boolean inTaglib = node.ancestors(ASTElement.class)
110
.anyMatch(elem -> elem.getNamespacePrefix() != null);
111
112
// Check if expression is sanitized
113
boolean sanitized = content.matches("^fn:escapeXml\\(.+\\)$");
114
115
if (!inTaglib && !sanitized) {
116
((RuleContext) data).addViolation(node,
117
"EL expression may be vulnerable to XSS attacks. " +
118
"Use fn:escapeXml() or place within a taglib element.");
119
}
120
121
return super.visit(node, data);
122
}
123
}
124
```
125
126
### Multi-Node Analysis
127
128
```java
129
import java.util.*;
130
131
public class DuplicateImportsRule extends AbstractJspRule {
132
133
private Set<String> seenImports = new HashSet<>();
134
135
@Override
136
public Object visit(ASTJspDirective node, Object data) {
137
if ("page".equals(node.getName())) {
138
// Find import attributes
139
node.children(ASTJspDirectiveAttribute.class)
140
.filter(attr -> "import".equals(attr.getName()))
141
.forEach(attr -> checkDuplicateImport(attr, data));
142
}
143
144
return super.visit(node, data);
145
}
146
147
private void checkDuplicateImport(ASTJspDirectiveAttribute attr, Object data) {
148
String importValue = attr.getValue();
149
150
if (!seenImports.add(importValue)) {
151
((RuleContext) data).addViolation(attr,
152
"Duplicate import: " + importValue);
153
}
154
}
155
156
@Override
157
public void start(RuleContext ctx) {
158
super.start(ctx);
159
seenImports.clear(); // Reset for each file
160
}
161
}
162
```
163
164
### Structural Analysis Rules
165
166
```java
167
public class NestedJsfInLoopRule extends AbstractJspRule {
168
169
@Override
170
public Object visit(ASTElement node, Object data) {
171
// Check for JSTL iteration tags
172
if (isJstlIterationTag(node)) {
173
// Look for JSF components within iteration
174
node.descendants(ASTElement.class)
175
.filter(this::isJsfComponent)
176
.forEach(jsfElement -> {
177
((RuleContext) data).addViolation(jsfElement,
178
"JSF component found within JSTL iteration. " +
179
"This can cause performance issues.");
180
});
181
}
182
183
return super.visit(node, data);
184
}
185
186
private boolean isJstlIterationTag(ASTElement element) {
187
return "c".equals(element.getNamespacePrefix()) &&
188
("forEach".equals(element.getLocalName()) ||
189
"forTokens".equals(element.getLocalName()));
190
}
191
192
private boolean isJsfComponent(ASTElement element) {
193
String prefix = element.getNamespacePrefix();
194
return "h".equals(prefix) || "f".equals(prefix);
195
}
196
}
197
```
198
199
## Built-in Rule Examples
200
201
### Security Rule Implementation
202
203
Based on the existing `NoUnsanitizedJSPExpressionRule`:
204
205
```java { .api }
206
public class NoUnsanitizedJSPExpressionRule extends AbstractJspRule {
207
public Object visit(ASTElExpression node, Object data);
208
}
209
```
210
211
**Implementation pattern:**
212
213
```java
214
@Override
215
public Object visit(ASTElExpression node, Object data) {
216
if (elOutsideTaglib(node)) {
217
((RuleContext) data).addViolation(node);
218
}
219
return super.visit(node, data);
220
}
221
222
private boolean elOutsideTaglib(ASTElExpression node) {
223
ASTElement parentElement = node.ancestors(ASTElement.class).first();
224
225
boolean elInTaglib = parentElement != null &&
226
parentElement.getName() != null &&
227
parentElement.getName().contains(":");
228
229
boolean elWithFnEscapeXml = node.getContent() != null &&
230
node.getContent().matches("^fn:escapeXml\\(.+\\)$");
231
232
return !elInTaglib && !elWithFnEscapeXml;
233
}
234
```
235
236
### Design Rules
237
238
```java { .api }
239
public class NoInlineStyleInformationRule extends AbstractJspRule {
240
// Implementation for detecting inline style attributes
241
}
242
243
public class NoLongScriptsRule extends AbstractJspRule {
244
// Implementation for detecting overly long scriptlets
245
}
246
247
public class NoScriptletsRule extends AbstractJspRule {
248
// Implementation for detecting JSP scriptlets
249
}
250
```
251
252
### Code Style Rules
253
254
```java { .api }
255
public class DuplicateJspImportsRule extends AbstractJspRule {
256
// Implementation for detecting duplicate import directives
257
}
258
```
259
260
## Rule Configuration
261
262
### XML Rule Definition
263
264
Rules are defined in XML files under `/category/jsp/`:
265
266
```xml
267
<rule name="NoUnsanitizedJSPExpression"
268
language="jsp"
269
since="3.6"
270
message="Avoid unsanitized JSP expressions"
271
class="net.sourceforge.pmd.lang.jsp.rule.security.NoUnsanitizedJSPExpressionRule"
272
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_security.html#nounsanitizedjspexpression">
273
<description>
274
Avoid unsanitized JSP expressions as they can lead to Cross Site Scripting (XSS) attacks.
275
</description>
276
<priority>2</priority>
277
<example>
278
<![CDATA[
279
<!-- Bad: Unsanitized EL expression -->
280
<p>${userInput}</p>
281
282
<!-- Good: Sanitized with fn:escapeXml -->
283
<p>${fn:escapeXml(userInput)}</p>
284
285
<!-- Good: Within taglib element -->
286
<c:out value="${userInput}"/>
287
]]>
288
</example>
289
</rule>
290
```
291
292
### XPath-based Rules
293
294
Some rules use XPath expressions instead of Java implementations:
295
296
```xml
297
<rule name="NoClassAttribute"
298
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
299
<properties>
300
<property name="xpath">
301
<value><![CDATA[
302
//Attribute[ upper-case(@Name)="CLASS" ]
303
]]></value>
304
</property>
305
</properties>
306
</rule>
307
```
308
309
## Advanced Rule Techniques
310
311
### Rule with Properties
312
313
```java
314
public class ConfigurableRule extends AbstractJspRule {
315
316
private static final StringProperty MAX_LENGTH =
317
StringProperty.named("maxLength")
318
.desc("Maximum allowed length")
319
.defaultValue("100")
320
.build();
321
322
public ConfigurableRule() {
323
definePropertyDescriptor(MAX_LENGTH);
324
}
325
326
@Override
327
public Object visit(ASTJspScriptlet node, Object data) {
328
int maxLength = Integer.parseInt(getProperty(MAX_LENGTH));
329
330
if (node.getContent().length() > maxLength) {
331
((RuleContext) data).addViolation(node,
332
"Scriptlet exceeds maximum length of " + maxLength);
333
}
334
335
return super.visit(node, data);
336
}
337
}
338
```
339
340
### Rule with State Tracking
341
342
```java
343
public class StatefulRule extends AbstractJspRule {
344
345
private Map<String, Integer> elementCounts = new HashMap<>();
346
347
@Override
348
public void start(RuleContext ctx) {
349
super.start(ctx);
350
elementCounts.clear();
351
}
352
353
@Override
354
public Object visit(ASTElement node, Object data) {
355
String elementName = node.getName();
356
int count = elementCounts.getOrDefault(elementName, 0) + 1;
357
elementCounts.put(elementName, count);
358
359
if (count > 10) {
360
((RuleContext) data).addViolation(node,
361
"Too many " + elementName + " elements (" + count + ")");
362
}
363
364
return super.visit(node, data);
365
}
366
}
367
```
368
369
### Complex Analysis Rule
370
371
```java
372
public class ComplexAnalysisRule extends AbstractJspRule {
373
374
private Stack<String> contextStack = new Stack<>();
375
private boolean inFormContext = false;
376
377
@Override
378
public Object visit(ASTElement node, Object data) {
379
String elementName = node.getName();
380
381
// Track context
382
if ("form".equals(elementName)) {
383
inFormContext = true;
384
contextStack.push("form");
385
}
386
387
// Analyze based on context
388
if ("input".equals(elementName) && !inFormContext) {
389
((RuleContext) data).addViolation(node,
390
"Input element found outside of form context");
391
}
392
393
// Continue traversal
394
Object result = super.visit(node, data);
395
396
// Clean up context
397
if ("form".equals(elementName)) {
398
contextStack.pop();
399
inFormContext = !contextStack.contains("form");
400
}
401
402
return result;
403
}
404
}
405
```
406
407
## Testing Rules
408
409
### Unit Testing Pattern
410
411
```java
412
import net.sourceforge.pmd.test.SimpleAggregatorTst;
413
414
public class MyJspRuleTest extends SimpleAggregatorTst {
415
416
private static final String TEST_JSP =
417
"<%@ page language=\"java\" %>\n" +
418
"<html>\n" +
419
" <body>\n" +
420
" ${unsafeExpression}\n" +
421
" </body>\n" +
422
"</html>";
423
424
@Test
425
public void testUnsafeExpression() {
426
Rule rule = new NoUnsanitizedJSPExpressionRule();
427
RuleViolation[] violations = getViolations(rule, TEST_JSP);
428
429
assertEquals(1, violations.length);
430
assertTrue(violations[0].getDescription().contains("unsafe"));
431
}
432
}
433
```
434
435
## Rule Categories
436
437
Rules are organized into categories:
438
439
- **bestpractices**: General best practices
440
- **security**: Security-related issues
441
- **design**: Design and architecture issues
442
- **codestyle**: Code style and formatting
443
- **errorprone**: Error-prone patterns
444
- **performance**: Performance issues
445
- **multithreading**: Multithreading issues
446
- **documentation**: Documentation issues
447
448
Each category has its own XML ruleset file that can be included or excluded in PMD analysis configurations.
449
450
## Integration with PMD Analysis
451
452
Rules integrate with PMD's analysis engine:
453
454
1. **Rule Discovery**: Rules are discovered through XML ruleset files
455
2. **AST Application**: Rules are applied to JSP AST nodes via visitor pattern
456
3. **Violation Reporting**: Violations are reported through RuleContext
457
4. **Result Aggregation**: PMD aggregates violations across all rules and files
458
459
The rule framework provides a powerful foundation for implementing custom JSP code quality checks that integrate seamlessly with PMD's analysis pipeline.