or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ast-processing.mdcopy-paste-detection.mdcore-analysis.mdindex.mdlanguage-framework.mdproperties-system.mdrendering-system.mdreporting-system.mdrule-system.mdutilities.md

reporting-system.mddocs/

0

# Reporting System

1

2

The Reporting System provides comprehensive infrastructure for collecting, managing, and processing PMD analysis results. It includes violation tracking, error handling, event-driven processing, and statistical reporting capabilities.

3

4

## Capabilities

5

6

### Report Management

7

8

Central report class that collects and manages all PMD analysis results including violations, errors, and statistics.

9

10

```java { .api }

11

/**

12

* Collects all PMD analysis results including violations, errors, and metrics.

13

* Provides filtering, merging, and statistical analysis capabilities.

14

*/

15

public final class Report {

16

17

/**

18

* Build report programmatically using builder pattern

19

* @param lambda Function to configure report builder

20

* @return Constructed Report with specified content

21

*/

22

static Report buildReport(Consumer<? super FileAnalysisListener> lambda);

23

24

/**

25

* Get all rule violations found during analysis

26

* @return Unmodifiable list of RuleViolation instances

27

*/

28

List<RuleViolation> getViolations();

29

30

/**

31

* Get violations that were suppressed by comments or configuration

32

* @return List of SuppressedViolation instances

33

*/

34

List<SuppressedViolation> getSuppressedViolations();

35

36

/**

37

* Get processing errors that occurred during analysis

38

* @return List of ProcessingError instances for files that failed to process

39

*/

40

List<ProcessingError> getProcessingErrors();

41

42

/**

43

* Get configuration errors from rule loading or setup

44

* @return List of ConfigurationError instances

45

*/

46

List<ConfigurationError> getConfigurationErrors();

47

48

/**

49

* Create filtered report containing only violations matching predicate

50

* @param filter Predicate to test each violation

51

* @return New Report containing only matching violations

52

*/

53

Report filterViolations(Predicate<RuleViolation> filter);

54

55

/**

56

* Merge this report with another report

57

* @param other Report to merge with this one

58

* @return New Report containing combined results

59

*/

60

Report union(Report other);

61

62

/**

63

* Get statistical summary of report contents

64

* @return ReportStats with counts and analysis metrics

65

*/

66

ReportStats getStats();

67

68

/**

69

* Check if report contains any violations or errors

70

* @return true if report has no violations, errors, or other issues

71

*/

72

boolean isEmpty();

73

74

/**

75

* Configuration error during rule setup or loading

76

*/

77

static final class ConfigurationError {

78

Rule getRule();

79

String getIssue();

80

Throwable getCause();

81

}

82

83

/**

84

* Processing error for files that failed analysis

85

*/

86

static final class ProcessingError {

87

Throwable getError();

88

String getFile();

89

String getMessage();

90

}

91

92

/**

93

* Violation that was suppressed by user configuration

94

*/

95

static final class SuppressedViolation {

96

RuleViolation getRuleViolation();

97

String getSuppressedByUser();

98

String getSuppressedByRule();

99

}

100

101

/**

102

* Builder interface for programmatic report construction

103

*/

104

interface GlobalReportBuilderListener {

105

void onRuleViolation(RuleViolation violation);

106

void onSuppressedRuleViolation(SuppressedViolation violation);

107

void onProcessingError(ProcessingError error);

108

void onConfigurationError(ConfigurationError error);

109

}

110

111

/**

112

* File-specific builder for incremental report construction

113

*/

114

interface ReportBuilderListener {

115

void onRuleViolation(RuleViolation violation);

116

void onSuppressedRuleViolation(SuppressedViolation violation);

117

void onError(ProcessingError error);

118

}

119

}

120

```

121

122

**Usage Examples:**

123

124

```java

125

import net.sourceforge.pmd.reporting.*;

126

import java.util.List;

127

import java.util.function.Predicate;

128

129

// Working with analysis reports

130

public class ReportAnalysisExamples {

131

132

public void analyzeReport(Report report) {

133

// Get basic statistics

134

ReportStats stats = report.getStats();

135

System.out.printf("Analysis Results:%n");

136

System.out.printf(" Violations: %d%n", stats.getNumViolations());

137

System.out.printf(" Errors: %d%n", stats.getNumErrors());

138

System.out.printf(" Processing Errors: %d%n", stats.getNumProcessingErrors());

139

System.out.printf(" Configuration Errors: %d%n", stats.getNumConfigErrors());

140

141

// Check if analysis was successful

142

if (report.isEmpty()) {

143

System.out.println("No issues found!");

144

} else {

145

processViolations(report);

146

processErrors(report);

147

}

148

}

149

150

public void processViolations(Report report) {

151

List<RuleViolation> violations = report.getViolations();

152

153

// Group violations by file

154

Map<String, List<RuleViolation>> violationsByFile = violations.stream()

155

.collect(Collectors.groupingBy(RuleViolation::getFilename));

156

157

violationsByFile.forEach((file, fileViolations) -> {

158

System.out.printf("%n%s (%d violations):%n", file, fileViolations.size());

159

fileViolations.forEach(violation -> {

160

System.out.printf(" Line %d: %s [%s]%n",

161

violation.getBeginLine(),

162

violation.getDescription(),

163

violation.getRule().getName());

164

});

165

});

166

167

// Show suppressed violations if any

168

List<Report.SuppressedViolation> suppressed = report.getSuppressedViolations();

169

if (!suppressed.isEmpty()) {

170

System.out.printf("%nSuppressed violations: %d%n", suppressed.size());

171

suppressed.forEach(sv -> {

172

RuleViolation violation = sv.getRuleViolation();

173

System.out.printf(" %s:%d - %s (suppressed by: %s)%n",

174

violation.getFilename(),

175

violation.getBeginLine(),

176

violation.getRule().getName(),

177

sv.getSuppressedByUser() != null ? "user" : "rule");

178

});

179

}

180

}

181

182

public void processErrors(Report report) {

183

// Handle processing errors

184

List<Report.ProcessingError> processingErrors = report.getProcessingErrors();

185

if (!processingErrors.isEmpty()) {

186

System.out.printf("%nProcessing errors: %d%n", processingErrors.size());

187

processingErrors.forEach(error -> {

188

System.out.printf(" %s: %s%n", error.getFile(), error.getMessage());

189

if (error.getError() != null) {

190

error.getError().printStackTrace();

191

}

192

});

193

}

194

195

// Handle configuration errors

196

List<Report.ConfigurationError> configErrors = report.getConfigurationErrors();

197

if (!configErrors.isEmpty()) {

198

System.out.printf("%nConfiguration errors: %d%n", configErrors.size());

199

configErrors.forEach(error -> {

200

System.out.printf(" Rule %s: %s%n",

201

error.getRule() != null ? error.getRule().getName() : "unknown",

202

error.getIssue());

203

});

204

}

205

}

206

207

public Report filterHighPriorityViolations(Report report) {

208

// Filter to only high priority violations

209

Predicate<RuleViolation> highPriorityFilter = violation ->

210

violation.getRule().getPriority() == RulePriority.HIGH ||

211

violation.getRule().getPriority() == RulePriority.MEDIUM_HIGH;

212

213

return report.filterViolations(highPriorityFilter);

214

}

215

216

public Report mergeReports(List<Report> reports) {

217

// Merge multiple reports into one

218

return reports.stream()

219

.reduce(Report.empty(), Report::union);

220

}

221

}

222

223

// Building custom reports programmatically

224

public Report buildCustomReport() {

225

return Report.buildReport(builder -> {

226

// Add custom violations

227

builder.onRuleViolation(createViolation("CustomRule", "Custom message", 42));

228

229

// Add processing error

230

builder.onProcessingError(new Report.ProcessingError(

231

new RuntimeException("Parse error"),

232

"BadFile.java",

233

"Syntax error in file"));

234

235

// Add configuration error

236

builder.onConfigurationError(new Report.ConfigurationError(

237

someRule,

238

"Property 'threshold' must be positive",

239

null));

240

});

241

}

242

```

243

244

### Rule Violation Interface

245

246

Interface representing individual rule violation instances with location, context, and rule information.

247

248

```java { .api }

249

/**

250

* Represents a rule violation instance with location and context information.

251

* Provides access to rule, location, and descriptive details.

252

*/

253

public interface RuleViolation {

254

255

/**

256

* Get the rule that was violated

257

* @return Rule instance that detected this violation

258

*/

259

Rule getRule();

260

261

/**

262

* Get violation description message

263

* @return Human-readable description of the violation

264

*/

265

String getDescription();

266

267

/**

268

* Check if violation is suppressed

269

* @return true if violation was suppressed by comments or configuration

270

*/

271

boolean isSuppressed();

272

273

/**

274

* Get source filename where violation occurred

275

* @return File path or name containing the violation

276

*/

277

String getFilename();

278

279

/**

280

* Get violation start line number

281

* @return One-based line number where violation begins

282

*/

283

int getBeginLine();

284

285

/**

286

* Get violation start column number

287

* @return One-based column number where violation begins

288

*/

289

int getBeginColumn();

290

291

/**

292

* Get violation end line number

293

* @return One-based line number where violation ends

294

*/

295

int getEndLine();

296

297

/**

298

* Get violation end column number

299

* @return One-based column number where violation ends

300

*/

301

int getEndColumn();

302

303

/**

304

* Get package name containing the violation

305

* @return Package name, or empty string if not applicable

306

*/

307

String getPackageName();

308

309

/**

310

* Get class name containing the violation

311

* @return Class name, or empty string if not applicable

312

*/

313

String getClassName();

314

315

/**

316

* Get method name containing the violation

317

* @return Method name, or empty string if not applicable

318

*/

319

String getMethodName();

320

321

/**

322

* Get variable name associated with violation

323

* @return Variable name, or empty string if not applicable

324

*/

325

String getVariableName();

326

}

327

```

328

329

**Usage Examples:**

330

331

```java

332

import net.sourceforge.pmd.reporting.RuleViolation;

333

334

// Processing rule violations

335

public class ViolationProcessor {

336

337

public void processViolation(RuleViolation violation) {

338

// Basic violation information

339

System.out.printf("Violation: %s%n", violation.getDescription());

340

System.out.printf("Rule: %s (Priority: %s)%n",

341

violation.getRule().getName(),

342

violation.getRule().getPriority().getName());

343

344

// Location information

345

System.out.printf("File: %s%n", violation.getFilename());

346

System.out.printf("Location: %d:%d-%d:%d%n",

347

violation.getBeginLine(), violation.getBeginColumn(),

348

violation.getEndLine(), violation.getEndColumn());

349

350

// Context information (if available)

351

if (!violation.getPackageName().isEmpty()) {

352

System.out.printf("Package: %s%n", violation.getPackageName());

353

}

354

if (!violation.getClassName().isEmpty()) {

355

System.out.printf("Class: %s%n", violation.getClassName());

356

}

357

if (!violation.getMethodName().isEmpty()) {

358

System.out.printf("Method: %s%n", violation.getMethodName());

359

}

360

if (!violation.getVariableName().isEmpty()) {

361

System.out.printf("Variable: %s%n", violation.getVariableName());

362

}

363

364

// Check suppression status

365

if (violation.isSuppressed()) {

366

System.out.println("Note: This violation is suppressed");

367

}

368

}

369

370

public void generateViolationReport(List<RuleViolation> violations) {

371

// Sort violations by file and line number

372

violations.sort(Comparator

373

.comparing(RuleViolation::getFilename)

374

.thenComparing(RuleViolation::getBeginLine));

375

376

String currentFile = "";

377

for (RuleViolation violation : violations) {

378

if (!violation.getFilename().equals(currentFile)) {

379

currentFile = violation.getFilename();

380

System.out.printf("%n=== %s ===%n", currentFile);

381

}

382

383

System.out.printf("Line %d: %s [%s]%n",

384

violation.getBeginLine(),

385

violation.getDescription(),

386

violation.getRule().getName());

387

}

388

}

389

390

public Map<String, List<RuleViolation>> groupByRule(List<RuleViolation> violations) {

391

return violations.stream()

392

.collect(Collectors.groupingBy(v -> v.getRule().getName()));

393

}

394

395

public Map<String, Long> countByPriority(List<RuleViolation> violations) {

396

return violations.stream()

397

.collect(Collectors.groupingBy(

398

v -> v.getRule().getPriority().getName(),

399

Collectors.counting()));

400

}

401

}

402

```

403

404

### Analysis Event Listeners

405

406

Event-driven interfaces for receiving real-time analysis events and custom result processing.

407

408

```java { .api }

409

/**

410

* Receives events during PMD analysis for custom processing.

411

* Implements AutoCloseable for proper resource management.

412

*/

413

public interface GlobalAnalysisListener extends AutoCloseable {

414

415

/**

416

* Get listener initializer for setup phase

417

* @return ListenerInitializer for configuration

418

*/

419

ListenerInitializer initializer();

420

421

/**

422

* Start analysis of specific file

423

* @param file TextFile being analyzed

424

* @return FileAnalysisListener for file-specific events

425

*/

426

FileAnalysisListener startFileAnalysis(TextFile file);

427

428

/**

429

* Handle configuration error

430

* @param error ConfigurationError that occurred during setup

431

*/

432

void onConfigError(Report.ConfigurationError error);

433

434

/**

435

* Create no-operation listener that ignores all events

436

* @return GlobalAnalysisListener that performs no actions

437

*/

438

static GlobalAnalysisListener noop();

439

440

/**

441

* Combine multiple listeners into single listener (tee pattern)

442

* @param listeners List of listeners to combine

443

* @return GlobalAnalysisListener that forwards events to all listeners

444

*/

445

static GlobalAnalysisListener tee(List<? extends GlobalAnalysisListener> listeners);

446

447

/**

448

* Close listener and cleanup resources

449

*/

450

void close();

451

}

452

453

/**

454

* Receives file-specific analysis events.

455

* Created by GlobalAnalysisListener for each file being analyzed.

456

*/

457

public interface FileAnalysisListener {

458

459

/**

460

* Handle rule violation found in current file

461

* @param violation RuleViolation detected during analysis

462

*/

463

void onRuleViolation(RuleViolation violation);

464

465

/**

466

* Handle suppressed rule violation

467

* @param violation SuppressedViolation that was suppressed

468

*/

469

void onSuppressedRuleViolation(Report.SuppressedViolation violation);

470

471

/**

472

* Handle processing error in current file

473

* @param error ProcessingError that occurred during file analysis

474

*/

475

void onError(Report.ProcessingError error);

476

}

477

478

/**

479

* Initializer for setting up analysis listeners.

480

*/

481

interface ListenerInitializer {

482

483

/**

484

* Initialize listener with analysis context

485

* @param ctx AnalysisContext with configuration and metadata

486

*/

487

void initialize(AnalysisContext ctx);

488

}

489

```

490

491

**Usage Examples:**

492

493

```java

494

import net.sourceforge.pmd.reporting.*;

495

import java.io.PrintWriter;

496

import java.util.concurrent.atomic.AtomicInteger;

497

498

// Custom listener for real-time violation processing

499

public class CustomAnalysisListener implements GlobalAnalysisListener {

500

private final PrintWriter output;

501

private final AtomicInteger totalViolations = new AtomicInteger(0);

502

private final AtomicInteger filesProcessed = new AtomicInteger(0);

503

504

public CustomAnalysisListener(PrintWriter output) {

505

this.output = output;

506

}

507

508

@Override

509

public ListenerInitializer initializer() {

510

return ctx -> {

511

output.println("Starting PMD analysis...");

512

output.printf("Analyzing %d files%n", ctx.getFileCount());

513

};

514

}

515

516

@Override

517

public FileAnalysisListener startFileAnalysis(TextFile file) {

518

filesProcessed.incrementAndGet();

519

output.printf("Analyzing: %s%n", file.getDisplayName());

520

521

return new FileAnalysisListener() {

522

private int fileViolations = 0;

523

524

@Override

525

public void onRuleViolation(RuleViolation violation) {

526

fileViolations++;

527

totalViolations.incrementAndGet();

528

529

output.printf(" Line %d: %s [%s]%n",

530

violation.getBeginLine(),

531

violation.getDescription(),

532

violation.getRule().getName());

533

}

534

535

@Override

536

public void onSuppressedRuleViolation(Report.SuppressedViolation violation) {

537

output.printf(" Suppressed: %s%n",

538

violation.getRuleViolation().getRule().getName());

539

}

540

541

@Override

542

public void onError(Report.ProcessingError error) {

543

output.printf(" ERROR: %s%n", error.getMessage());

544

}

545

};

546

}

547

548

@Override

549

public void onConfigError(Report.ConfigurationError error) {

550

output.printf("Configuration error: %s%n", error.getIssue());

551

}

552

553

@Override

554

public void close() {

555

output.printf("Analysis complete: %d violations in %d files%n",

556

totalViolations.get(), filesProcessed.get());

557

output.flush();

558

}

559

}

560

561

// Statistics collecting listener

562

public class StatisticsListener implements GlobalAnalysisListener {

563

private final Map<String, AtomicInteger> ruleViolationCounts = new ConcurrentHashMap<>();

564

private final Map<String, AtomicInteger> fileViolationCounts = new ConcurrentHashMap<>();

565

private final AtomicInteger totalErrors = new AtomicInteger(0);

566

567

@Override

568

public ListenerInitializer initializer() {

569

return ctx -> {

570

// Initialize statistics collection

571

ruleViolationCounts.clear();

572

fileViolationCounts.clear();

573

totalErrors.set(0);

574

};

575

}

576

577

@Override

578

public FileAnalysisListener startFileAnalysis(TextFile file) {

579

String fileName = file.getDisplayName();

580

fileViolationCounts.put(fileName, new AtomicInteger(0));

581

582

return new FileAnalysisListener() {

583

@Override

584

public void onRuleViolation(RuleViolation violation) {

585

String ruleName = violation.getRule().getName();

586

ruleViolationCounts.computeIfAbsent(ruleName, k -> new AtomicInteger(0))

587

.incrementAndGet();

588

fileViolationCounts.get(fileName).incrementAndGet();

589

}

590

591

@Override

592

public void onSuppressedRuleViolation(Report.SuppressedViolation violation) {

593

// Track suppressed violations separately if needed

594

}

595

596

@Override

597

public void onError(Report.ProcessingError error) {

598

totalErrors.incrementAndGet();

599

}

600

};

601

}

602

603

@Override

604

public void onConfigError(Report.ConfigurationError error) {

605

totalErrors.incrementAndGet();

606

}

607

608

public void printStatistics() {

609

System.out.println("Violation Statistics:");

610

ruleViolationCounts.entrySet().stream()

611

.sorted(Map.Entry.<String, AtomicInteger>comparingByValue(

612

(a, b) -> b.get() - a.get()))

613

.forEach(entry -> System.out.printf(" %s: %d%n",

614

entry.getKey(), entry.getValue().get()));

615

616

System.out.printf("Total errors: %d%n", totalErrors.get());

617

}

618

619

@Override

620

public void close() {

621

printStatistics();

622

}

623

}

624

625

// Usage with PmdAnalysis

626

try (PmdAnalysis analysis = PmdAnalysis.create(config)) {

627

// Add custom listeners

628

analysis.addListener(new CustomAnalysisListener(

629

new PrintWriter(System.out, true)));

630

analysis.addListener(new StatisticsListener());

631

632

// Add files and rules

633

analysis.files().addDirectory(Paths.get("src"));

634

analysis.addRuleSet(ruleSet);

635

636

// Run analysis - listeners receive events in real-time

637

analysis.performAnalysis();

638

}

639

```

640

641

## Types

642

643

```java { .api }

644

/**

645

* Statistical summary of report contents

646

*/

647

interface ReportStats {

648

649

/**

650

* Get total number of violations

651

* @return Count of all rule violations

652

*/

653

int getNumViolations();

654

655

/**

656

* Get total number of errors

657

* @return Count of all errors (processing + configuration)

658

*/

659

int getNumErrors();

660

661

/**

662

* Get number of processing errors

663

* @return Count of file processing errors

664

*/

665

int getNumProcessingErrors();

666

667

/**

668

* Get number of configuration errors

669

* @return Count of rule configuration errors

670

*/

671

int getNumConfigErrors();

672

673

/**

674

* Get violation counts by rule name

675

* @return Map of rule names to violation counts

676

*/

677

Map<String, Integer> getViolationsByRule();

678

679

/**

680

* Get violation counts by file

681

* @return Map of file names to violation counts

682

*/

683

Map<String, Integer> getViolationsByFile();

684

}

685

686

/**

687

* Analysis context provided to listeners

688

*/

689

interface AnalysisContext {

690

691

/**

692

* Get number of files to be analyzed

693

* @return Total file count for analysis

694

*/

695

int getFileCount();

696

697

/**

698

* Get PMD configuration

699

* @return PMDConfiguration used for analysis

700

*/

701

PMDConfiguration getConfiguration();

702

703

/**

704

* Get language registry

705

* @return LanguageRegistry with supported languages

706

*/

707

LanguageRegistry getLanguageRegistry();

708

709

/**

710

* Get active rulesets

711

* @return List of RuleSet instances being applied

712

*/

713

List<RuleSet> getRuleSets();

714

}

715

716

/**

717

* Factory for creating rule violations

718

*/

719

interface RuleViolationFactory {

720

721

/**

722

* Create violation for AST node

723

* @param rule Rule that detected violation

724

* @param node AST node where violation occurred

725

* @param message Violation message

726

* @return RuleViolation instance

727

*/

728

RuleViolation createViolation(Rule rule, Node node, String message);

729

730

/**

731

* Create violation with arguments for message formatting

732

* @param rule Rule that detected violation

733

* @param node AST node where violation occurred

734

* @param messageArgs Arguments for message template

735

* @return RuleViolation instance with formatted message

736

*/

737

RuleViolation createViolation(Rule rule, Node node, Object... messageArgs);

738

}

739

740

/**

741

* Thread-safe report builder for concurrent analysis

742

*/

743

interface ConcurrentReportBuilder {

744

745

/**

746

* Add violation to report (thread-safe)

747

* @param violation RuleViolation to add

748

*/

749

void addViolation(RuleViolation violation);

750

751

/**

752

* Add suppressed violation to report (thread-safe)

753

* @param violation SuppressedViolation to add

754

*/

755

void addSuppressedViolation(Report.SuppressedViolation violation);

756

757

/**

758

* Add processing error to report (thread-safe)

759

* @param error ProcessingError to add

760

*/

761

void addError(Report.ProcessingError error);

762

763

/**

764

* Build final report from collected data

765

* @return Report containing all collected violations and errors

766

*/

767

Report build();

768

}

769

```