or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ast-processing.mdindex.mdlanguage-support.mdmetrics.mdrule-framework.mdsymbols-types.md

rule-framework.mddocs/

0

# Rule Framework

1

2

PMD Java includes a comprehensive rule framework with over 400 built-in rules across 8 categories for detecting code quality issues, security vulnerabilities, and performance problems. The framework supports visitor-based rules, XPath-based custom rules, and provides infrastructure for developing custom rules.

3

4

## Capabilities

5

6

### Rule Categories

7

8

PMD Java organizes its 400+ built-in rules into eight main categories, each targeting specific aspects of code quality:

9

10

#### Best Practices (80+ rules)

11

Rules that enforce coding best practices and prevent common mistakes.

12

13

#### Code Style (60+ rules)

14

Rules that enforce consistent coding style and formatting conventions.

15

16

#### Design (30+ rules)

17

Rules that detect design problems and architectural issues.

18

19

#### Documentation (5+ rules)

20

Rules that enforce proper documentation and commenting standards.

21

22

#### Error Prone (120+ rules)

23

Rules that detect patterns that are likely to lead to bugs or runtime errors.

24

25

#### Multithreading (15+ rules)

26

Rules that detect concurrency and thread safety issues.

27

28

#### Performance (25+ rules)

29

Rules that identify performance problems and inefficient code patterns.

30

31

#### Security (5+ rules)

32

Rules that detect security vulnerabilities and unsafe practices.

33

34

### Rule Framework Architecture

35

36

```java { .api }

37

/**

38

* Base interface for all PMD rules

39

*/

40

public interface Rule {

41

/**

42

* Returns the name of this rule

43

*/

44

String getName();

45

46

/**

47

* Returns the description of this rule

48

*/

49

String getDescription();

50

51

/**

52

* Returns the message displayed when this rule is violated

53

*/

54

String getMessage();

55

56

/**

57

* Returns the external info URL for this rule

58

*/

59

String getExternalInfoUrl();

60

61

/**

62

* Returns the priority/severity of this rule

63

*/

64

RulePriority getPriority();

65

66

/**

67

* Applies this rule to the given node and reports violations

68

*/

69

void apply(List<? extends Node> nodes, RuleContext ctx);

70

71

/**

72

* Returns the language this rule applies to

73

*/

74

Language getLanguage();

75

76

/**

77

* Returns the minimum language version required for this rule

78

*/

79

LanguageVersion getMinimumLanguageVersion();

80

81

/**

82

* Returns the maximum language version supported by this rule

83

*/

84

LanguageVersion getMaximumLanguageVersion();

85

}

86

```

87

88

### Java Rule Base Class

89

90

```java { .api }

91

/**

92

* Base class for Java-specific rules using the visitor pattern

93

*/

94

public abstract class AbstractJavaRule extends AbstractRule implements JavaParserVisitor {

95

/**

96

* Default implementation that visits child nodes

97

*/

98

public Object visit(JavaNode node, Object data);

99

100

/**

101

* Visit method for compilation units (root nodes)

102

*/

103

public Object visit(ASTCompilationUnit node, Object data);

104

105

/**

106

* Visit method for class declarations

107

*/

108

public Object visit(ASTClassDeclaration node, Object data);

109

110

/**

111

* Visit method for method declarations

112

*/

113

public Object visit(ASTMethodDeclaration node, Object data);

114

115

/**

116

* Visit method for field declarations

117

*/

118

public Object visit(ASTFieldDeclaration node, Object data);

119

120

/**

121

* Visit method for method calls

122

*/

123

public Object visit(ASTMethodCall node, Object data);

124

125

/**

126

* Visit method for variable access

127

*/

128

public Object visit(ASTVariableAccess node, Object data);

129

130

// 180+ additional visit methods for all AST node types...

131

132

/**

133

* Convenience method to add a rule violation

134

*/

135

protected final void addViolation(Object data, JavaNode node);

136

137

/**

138

* Convenience method to add a rule violation with custom message

139

*/

140

protected final void addViolation(Object data, JavaNode node, String message);

141

142

/**

143

* Convenience method to add a rule violation with message arguments

144

*/

145

protected final void addViolation(Object data, JavaNode node, Object[] args);

146

}

147

```

148

149

### Rule Context and Violations

150

151

```java { .api }

152

/**

153

* Context provided to rules during execution

154

*/

155

public interface RuleContext {

156

/**

157

* Reports a rule violation

158

*/

159

void addViolation(RuleViolation violation);

160

161

/**

162

* Gets the current source file being analyzed

163

*/

164

TextFile getCurrentFile();

165

166

/**

167

* Gets the language version being processed

168

*/

169

LanguageVersion getLanguageVersion();

170

171

/**

172

* Gets configuration for the current rule

173

*/

174

RuleSetReferenceId getCurrentRuleSetReferenceId();

175

}

176

177

/**

178

* Represents a rule violation found in source code

179

*/

180

public interface RuleViolation {

181

/**

182

* Gets the rule that was violated

183

*/

184

Rule getRule();

185

186

/**

187

* Gets the violation message

188

*/

189

String getDescription();

190

191

/**

192

* Gets the source file containing the violation

193

*/

194

String getFilename();

195

196

/**

197

* Gets the line number of the violation

198

*/

199

int getBeginLine();

200

201

/**

202

* Gets the column number of the violation

203

*/

204

int getBeginColumn();

205

206

/**

207

* Gets the end line number of the violation

208

*/

209

int getEndLine();

210

211

/**

212

* Gets the end column number of the violation

213

*/

214

int getEndColumn();

215

216

/**

217

* Gets the priority/severity of the violation

218

*/

219

RulePriority getPriority();

220

}

221

```

222

223

## Visitor-Based Rules

224

225

### Simple Rule Example

226

227

```java { .api }

228

/**

229

* Example rule that detects empty catch blocks

230

*/

231

public class EmptyCatchBlockRule extends AbstractJavaRule {

232

233

@Override

234

public Object visit(ASTTryStatement node, Object data) {

235

// Check each catch clause

236

for (ASTCatchClause catchClause : node.getCatchClauses()) {

237

ASTBlock catchBlock = catchClause.getBody();

238

239

// Check if catch block is empty

240

if (catchBlock.isEmpty()) {

241

addViolation(data, catchClause, "Empty catch block");

242

}

243

}

244

245

return super.visit(node, data);

246

}

247

}

248

```

249

250

### Complex Rule with Symbol Resolution

251

252

```java { .api }

253

/**

254

* Example rule that detects unused private methods

255

*/

256

public class UnusedPrivateMethodRule extends AbstractJavaRule {

257

private Set<JMethodSymbol> privateMethods = new HashSet<>();

258

private Set<JMethodSymbol> calledMethods = new HashSet<>();

259

260

@Override

261

public Object visit(ASTCompilationUnit node, Object data) {

262

// Reset state for new compilation unit

263

privateMethods.clear();

264

calledMethods.clear();

265

266

// First pass: collect all private methods

267

super.visit(node, data);

268

269

// Report unused private methods

270

for (JMethodSymbol method : privateMethods) {

271

if (!calledMethods.contains(method) && !isSpecialMethod(method)) {

272

JavaNode methodNode = method.tryGetNode();

273

if (methodNode != null) {

274

addViolation(data, methodNode,

275

"Unused private method: " + method.getSimpleName());

276

}

277

}

278

}

279

280

return data;

281

}

282

283

@Override

284

public Object visit(ASTMethodDeclaration node, Object data) {

285

JMethodSymbol symbol = node.getSymbol();

286

287

// Collect private methods

288

if (symbol != null && node.getVisibility() == Visibility.PRIVATE) {

289

privateMethods.add(symbol);

290

}

291

292

return super.visit(node, data);

293

}

294

295

@Override

296

public Object visit(ASTMethodCall node, Object data) {

297

JMethodSymbol calledMethod = node.getMethodType();

298

299

// Track method calls

300

if (calledMethod != null && !calledMethod.isUnresolved()) {

301

calledMethods.add(calledMethod);

302

}

303

304

return super.visit(node, data);

305

}

306

307

private boolean isSpecialMethod(JMethodSymbol method) {

308

String name = method.getSimpleName();

309

return "readObject".equals(name) ||

310

"writeObject".equals(name) ||

311

"readResolve".equals(name) ||

312

"writeReplace".equals(name);

313

}

314

}

315

```

316

317

### Rules Using Type System

318

319

```java { .api }

320

/**

321

* Rule that detects assignment to parameters

322

*/

323

public class AvoidReassigningParametersRule extends AbstractJavaRule {

324

325

@Override

326

public Object visit(ASTAssignmentExpression node, Object data) {

327

// Check if left side is parameter assignment

328

ASTExpression leftSide = node.getLeftOperand();

329

330

if (leftSide instanceof ASTVariableAccess) {

331

ASTVariableAccess varAccess = (ASTVariableAccess) leftSide;

332

JVariableSymbol variable = varAccess.getReferencedSym();

333

334

// Check if it's a formal parameter

335

if (variable instanceof JFormalParamSymbol) {

336

addViolation(data, node,

337

"Avoid reassigning parameter: " + variable.getSimpleName());

338

}

339

}

340

341

return super.visit(node, data);

342

}

343

}

344

```

345

346

## XPath-Based Rules

347

348

PMD supports XPath expressions for creating rules without writing Java code.

349

350

### XPath Rule Configuration

351

352

```java { .api }

353

/**

354

* XPath-based rule implementation

355

*/

356

public class XPathRule extends AbstractRule implements JavaParserVisitor {

357

/**

358

* Sets the XPath expression for this rule

359

*/

360

public void setXPath(String xpath);

361

362

/**

363

* Gets the XPath expression

364

*/

365

public String getXPath();

366

367

/**

368

* Sets XPath version (1.0, 2.0, 3.1)

369

*/

370

public void setVersion(String version);

371

372

/**

373

* Executes the XPath query against the AST

374

*/

375

@Override

376

public Object visit(JavaNode node, Object data);

377

}

378

```

379

380

### XPath Rule Examples

381

382

**Detect System.out.println usage:**

383

```xpath

384

//ASTMethodCall[

385

@MethodName = 'println' and

386

ASTFieldAccess[@Name = 'out' and ASTFieldAccess[@Name = 'System']]

387

]

388

```

389

390

**Find methods with too many parameters:**

391

```xpath

392

//ASTMethodDeclaration[count(ASTFormalParameters/ASTFormalParameter) > 5]

393

```

394

395

**Detect empty if blocks:**

396

```xpath

397

//ASTIfStatement[ASTBlock[count(*) = 0]]

398

```

399

400

**Find catch blocks that only log and rethrow:**

401

```xpath

402

//ASTCatchClause[

403

count(ASTBlock/*) = 2 and

404

ASTBlock/ASTExpressionStatement/ASTMethodCall[@MethodName = 'log'] and

405

ASTBlock/ASTThrowStatement

406

]

407

```

408

409

## Rule Execution Framework

410

411

### Rule Processing Pipeline

412

413

```java { .api }

414

/**

415

* Main entry point for rule execution

416

*/

417

public class JavaLanguageModule extends LanguageModuleBase {

418

/**

419

* Creates a language processor for executing rules

420

*/

421

@Override

422

public LanguageProcessor createProcessor(LanguagePropertyBundle bundle) {

423

return new JavaLanguageProcessor(bundle);

424

}

425

}

426

427

/**

428

* Language processor that coordinates rule execution

429

*/

430

public class JavaLanguageProcessor implements LanguageProcessor {

431

/**

432

* Processes source files and applies rules

433

*/

434

public void processSource(TextFile textFile, RuleSet ruleSet, RuleContext ruleContext);

435

436

/**

437

* Creates services for symbol resolution and type checking

438

*/

439

public AnalysisServices services();

440

}

441

442

/**

443

* Services available during rule execution

444

*/

445

public interface AnalysisServices {

446

/**

447

* Gets the symbol resolver for the current analysis

448

*/

449

SymbolResolver getSymbolResolver();

450

451

/**

452

* Gets the type resolver for the current analysis

453

*/

454

TypeResolver getTypeResolver();

455

456

/**

457

* Gets the semantic model (symbol table + type system)

458

*/

459

SemanticModel getSemanticModel();

460

}

461

```

462

463

### Rule Execution Context

464

465

```java { .api }

466

/**

467

* Extended context for Java rule execution

468

*/

469

public interface JavaRuleContext extends RuleContext {

470

/**

471

* Gets the parsed AST for the current file

472

*/

473

ASTCompilationUnit getCompilationUnit();

474

475

/**

476

* Gets the symbol table for symbol resolution

477

*/

478

JSymbolTable getSymbolTable();

479

480

/**

481

* Gets the type system for type analysis

482

*/

483

TypeSystem getTypeSystem();

484

485

/**

486

* Gets semantic analysis services

487

*/

488

AnalysisServices getAnalysisServices();

489

}

490

```

491

492

## Rule Configuration

493

494

### Rule Properties

495

496

```java { .api }

497

/**

498

* Base class for rule properties

499

*/

500

public abstract class PropertyDescriptor<T> {

501

/**

502

* Gets the name of this property

503

*/

504

public String name();

505

506

/**

507

* Gets the description of this property

508

*/

509

public String description();

510

511

/**

512

* Gets the default value

513

*/

514

public T defaultValue();

515

516

/**

517

* Validates a property value

518

*/

519

public String errorFor(T value);

520

}

521

522

/**

523

* Property descriptor for integer values

524

*/

525

public class IntegerPropertyDescriptor extends PropertyDescriptor<Integer> {

526

/**

527

* Creates descriptor with range validation

528

*/

529

public static IntegerPropertyDescriptor named(String name)

530

.desc(String description)

531

.range(int min, int max)

532

.defaultValue(int defaultValue);

533

}

534

535

/**

536

* Property descriptor for string values

537

*/

538

public class StringPropertyDescriptor extends PropertyDescriptor<String> {

539

/**

540

* Creates descriptor with regex validation

541

*/

542

public static StringPropertyDescriptor named(String name)

543

.desc(String description)

544

.validValues(String... values)

545

.defaultValue(String defaultValue);

546

}

547

```

548

549

### Configurable Rule Example

550

551

```java { .api }

552

/**

553

* Rule with configurable properties

554

*/

555

public class CyclomaticComplexityRule extends AbstractJavaRule {

556

557

// Property descriptors

558

private static final IntegerPropertyDescriptor CYCLO_THRESHOLD =

559

IntegerPropertyDescriptor.named("cyclomaticComplexity")

560

.desc("Maximum cyclomatic complexity before reporting violation")

561

.range(1, 50)

562

.defaultValue(10);

563

564

private static final BooleanPropertyDescriptor IGNORE_BOOLEAN_PATHS =

565

BooleanPropertyDescriptor.named("ignoreBooleanPaths")

566

.desc("Ignore boolean expressions in complexity calculation")

567

.defaultValue(false);

568

569

// Define properties for this rule

570

public CyclomaticComplexityRule() {

571

definePropertyDescriptor(CYCLO_THRESHOLD);

572

definePropertyDescriptor(IGNORE_BOOLEAN_PATHS);

573

}

574

575

@Override

576

public Object visit(ASTMethodDeclaration node, Object data) {

577

// Get configured property values

578

int threshold = getProperty(CYCLO_THRESHOLD);

579

boolean ignoreBooleanPaths = getProperty(IGNORE_BOOLEAN_PATHS);

580

581

// Create metric options based on properties

582

MetricOptions options = ignoreBooleanPaths ?

583

MetricOptions.ofOption(CycloOption.IGNORE_BOOLEAN_PATHS) :

584

MetricOptions.emptyOptions();

585

586

// Calculate complexity

587

int complexity = JavaMetrics.CYCLO.computeFor(node, options);

588

589

if (complexity > threshold) {

590

addViolation(data, node, new Object[] {

591

node.getName(),

592

complexity,

593

threshold

594

});

595

}

596

597

return super.visit(node, data);

598

}

599

}

600

```

601

602

## Built-in Rule Categories

603

604

### Best Practices Rules

605

606

Key rules in the best practices category:

607

608

- **AvoidReassigningParameters**: Avoid reassigning method parameters

609

- **OneDeclarationPerLine**: Declare variables one per line

610

- **UseCollectionIsEmpty**: Use isEmpty() instead of size() == 0

611

- **SystemPrintln**: Avoid System.out.println in production code

612

- **UnusedImports**: Remove unused import statements

613

- **UnusedLocalVariable**: Remove unused local variables

614

- **UnusedPrivateField**: Remove unused private fields

615

- **UnusedPrivateMethod**: Remove unused private methods

616

- **AvoidStringBufferField**: StringBuffer fields should be local variables

617

618

### Error Prone Rules

619

620

Critical rules that detect likely bugs:

621

622

- **AvoidBranchingStatementAsLastInLoop**: Avoid break/continue as last statement

623

- **AvoidDecimalLiteralsInBigDecimalConstructor**: Use string constructor for BigDecimal

624

- **BrokenNullCheck**: Detect broken null checks

625

- **EmptyCatchBlock**: Avoid empty catch blocks

626

- **NullPointerException**: Detect potential NPE scenarios

627

- **UseEqualsToCompareStrings**: Use equals() for string comparison

628

- **MissingBreakInSwitch**: Detect missing break in switch cases

629

- **OverrideBothEqualsAndHashcode**: Override both equals() and hashCode()

630

631

### Performance Rules

632

633

Rules that identify performance issues:

634

635

- **AvoidInstantiatingObjectsInLoops**: Avoid creating objects in loops

636

- **StringInstantiation**: Use string literals instead of new String()

637

- **UseStringBufferForStringAppends**: Use StringBuilder for concatenation

638

- **AvoidArrayLoops**: Use System.arraycopy() for array copying

639

- **BigIntegerInstantiation**: Use BigInteger constants for common values

640

- **BooleanInstantiation**: Use Boolean.TRUE/FALSE constants

641

642

### Security Rules

643

644

Rules that detect security vulnerabilities:

645

646

- **HardCodedCryptoKey**: Avoid hard-coded cryptographic keys

647

- **InsecureCryptoIv**: Use secure initialization vectors

648

- **AvoidFileStream**: Use nio.file APIs for file operations

649

- **StaticEJBFieldShouldBeFinal**: EJB static fields should be final

650

- **DoNotCallGarbageCollection**: Avoid System.gc() calls

651

652

## Usage Examples

653

654

### Creating a Custom Rule

655

656

```java

657

// Example: Rule to detect long parameter lists

658

public class TooManyParametersRule extends AbstractJavaRule {

659

660

private static final IntegerPropertyDescriptor MAX_PARAMETERS =

661

IntegerPropertyDescriptor.named("maxParameters")

662

.desc("Maximum number of parameters allowed")

663

.range(1, 20)

664

.defaultValue(7);

665

666

public TooManyParametersRule() {

667

definePropertyDescriptor(MAX_PARAMETERS);

668

}

669

670

@Override

671

public Object visit(ASTMethodDeclaration node, Object data) {

672

int maxParams = getProperty(MAX_PARAMETERS);

673

ASTFormalParameters params = node.getFormalParameters();

674

675

int paramCount = params.size();

676

if (paramCount > maxParams) {

677

addViolation(data, node, new Object[] {

678

node.getName(),

679

paramCount,

680

maxParams

681

});

682

}

683

684

return super.visit(node, data);

685

}

686

687

@Override

688

public Object visit(ASTConstructorDeclaration node, Object data) {

689

int maxParams = getProperty(MAX_PARAMETERS);

690

ASTFormalParameters params = node.getFormalParameters();

691

692

int paramCount = params.size();

693

if (paramCount > maxParams) {

694

addViolation(data, node, new Object[] {

695

"constructor",

696

paramCount,

697

maxParams

698

});

699

}

700

701

return super.visit(node, data);

702

}

703

}

704

```

705

706

### Rule with Symbol Analysis

707

708

```java

709

// Example: Rule to detect fields that should be local variables

710

public class FieldShouldBeLocalRule extends AbstractJavaRule {

711

private Map<JFieldSymbol, ASTFieldDeclaration> privateFields = new HashMap<>();

712

private Set<JFieldSymbol> accessedFields = new HashSet<>();

713

714

@Override

715

public Object visit(ASTCompilationUnit node, Object data) {

716

privateFields.clear();

717

accessedFields.clear();

718

719

// First pass: collect field info and usage

720

super.visit(node, data);

721

722

// Report fields that are only used in one method

723

for (Map.Entry<JFieldSymbol, ASTFieldDeclaration> entry : privateFields.entrySet()) {

724

JFieldSymbol field = entry.getKey();

725

ASTFieldDeclaration fieldDecl = entry.getValue();

726

727

if (!field.isStatic() && !accessedFields.contains(field)) {

728

Set<ASTMethodDeclaration> usingMethods = findMethodsUsingField(node, field);

729

730

if (usingMethods.size() == 1) {

731

addViolation(data, fieldDecl,

732

"Field '" + field.getSimpleName() +

733

"' is only used in one method and should be a local variable");

734

}

735

}

736

}

737

738

return data;

739

}

740

741

@Override

742

public Object visit(ASTFieldDeclaration node, Object data) {

743

if (node.getVisibility() == Visibility.PRIVATE) {

744

for (ASTVariableId varId : node.getVarIds()) {

745

JVariableSymbol symbol = varId.getSymbol();

746

if (symbol instanceof JFieldSymbol) {

747

privateFields.put((JFieldSymbol) symbol, node);

748

}

749

}

750

}

751

752

return super.visit(node, data);

753

}

754

755

@Override

756

public Object visit(ASTFieldAccess node, Object data) {

757

JFieldSymbol field = node.getReferencedSym();

758

if (field != null && privateFields.containsKey(field)) {

759

accessedFields.add(field);

760

}

761

762

return super.visit(node, data);

763

}

764

765

private Set<ASTMethodDeclaration> findMethodsUsingField(ASTCompilationUnit cu, JFieldSymbol field) {

766

FieldUsageVisitor visitor = new FieldUsageVisitor(field);

767

cu.acceptVisitor(visitor, null);

768

return visitor.getUsingMethods();

769

}

770

771

private static class FieldUsageVisitor extends JavaVisitorBase<Void, Void> {

772

private final JFieldSymbol targetField;

773

private final Set<ASTMethodDeclaration> usingMethods = new HashSet<>();

774

private ASTMethodDeclaration currentMethod;

775

776

public FieldUsageVisitor(JFieldSymbol field) {

777

this.targetField = field;

778

}

779

780

@Override

781

public Void visit(ASTMethodDeclaration node, Void data) {

782

ASTMethodDeclaration oldMethod = currentMethod;

783

currentMethod = node;

784

super.visit(node, data);

785

currentMethod = oldMethod;

786

return null;

787

}

788

789

@Override

790

public Void visit(ASTFieldAccess node, Void data) {

791

if (targetField.equals(node.getReferencedSym()) && currentMethod != null) {

792

usingMethods.add(currentMethod);

793

}

794

return super.visit(node, data);

795

}

796

797

public Set<ASTMethodDeclaration> getUsingMethods() {

798

return usingMethods;

799

}

800

}

801

}

802

```

803

804

### XPath Rule Registration

805

806

```java

807

// Example: Creating XPath rules programmatically

808

public class CustomXPathRules {

809

810

public static Rule createEmptyIfRule() {

811

XPathRule rule = new XPathRule();

812

rule.setName("EmptyIfStatement");

813

rule.setMessage("Empty if statement");

814

rule.setDescription("Detects empty if statements");

815

rule.setXPath("//ASTIfStatement[ASTBlock[count(*) = 0]]");

816

rule.setLanguage(JavaLanguageModule.getInstance());

817

return rule;

818

}

819

820

public static Rule createTooManyParametersRule() {

821

XPathRule rule = new XPathRule();

822

rule.setName("TooManyParameters");

823

rule.setMessage("Too many parameters: {0}");

824

rule.setDescription("Methods should not have too many parameters");

825

rule.setXPath("//ASTMethodDeclaration[count(.//ASTFormalParameter) > 7]");

826

rule.setLanguage(JavaLanguageModule.getInstance());

827

return rule;

828

}

829

830

public static Rule createStringLiteralEqualityRule() {

831

XPathRule rule = new XPathRule();

832

rule.setName("StringLiteralEquality");

833

rule.setMessage("Use equals() instead of == for string comparison");

834

rule.setDescription("String literals should be compared using equals() method");

835

rule.setXPath("""

836

//ASTEqualityExpression[@Operator = '==' or @Operator = '!=']

837

[ASTStringLiteral or

838

.//*[self::ASTMethodCall[@MethodName = 'toString'] or

839

self::ASTMethodCall[@MethodName = 'trim'] or

840

self::ASTMethodCall[@MethodName = 'toLowerCase'] or

841

self::ASTMethodCall[@MethodName = 'toUpperCase']]]

842

""");

843

rule.setLanguage(JavaLanguageModule.getInstance());

844

return rule;

845

}

846

}

847

```