or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ast-node-types.mdcopy-paste-detection.mdindex.mdjsp-parser-ast.mdlanguage-module.mdrule-development.mdvisitor-pattern.md

rule-development.mddocs/

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.