or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ast-nodes.mdbuiltin-rules.mdindex.mdlanguage-integration.mdparsing.mdrule-development.md

rule-development.mddocs/

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

```