0
# Rule System
1
2
The Rule System provides comprehensive infrastructure for defining, configuring, and executing static analysis rules. It includes rule interfaces, collection management, priority handling, loading mechanisms, and property-based configuration.
3
4
## Capabilities
5
6
### Rule Interface
7
8
Core interface for PMD rules with configuration, execution, and lifecycle management capabilities.
9
10
```java { .api }
11
/**
12
* Core interface for PMD rules with configuration and execution capabilities.
13
* Extends PropertySource to support configurable properties.
14
*/
15
public interface Rule extends PropertySource {
16
17
/**
18
* Suppress violations by regex pattern property descriptor
19
*/
20
PropertyDescriptor<Optional<Pattern>> VIOLATION_SUPPRESS_REGEX_DESCRIPTOR;
21
22
/**
23
* Suppress violations by XPath expression property descriptor
24
*/
25
PropertyDescriptor<Optional<String>> VIOLATION_SUPPRESS_XPATH_DESCRIPTOR;
26
27
/**
28
* Get rule's target language
29
* @return Language this rule analyzes
30
*/
31
Language getLanguage();
32
33
/**
34
* Set rule's target language
35
* @param language Language for this rule to analyze
36
*/
37
void setLanguage(Language language);
38
39
/**
40
* Get minimum language version required by rule
41
* @return Minimum LanguageVersion, or null if no minimum
42
*/
43
LanguageVersion getMinimumLanguageVersion();
44
45
/**
46
* Set minimum language version required
47
* @param version Minimum LanguageVersion required
48
*/
49
void setMinimumLanguageVersion(LanguageVersion version);
50
51
/**
52
* Get maximum language version supported by rule
53
* @return Maximum LanguageVersion, or null if no maximum
54
*/
55
LanguageVersion getMaximumLanguageVersion();
56
57
/**
58
* Set maximum language version supported
59
* @param version Maximum LanguageVersion supported
60
*/
61
void setMaximumLanguageVersion(LanguageVersion version);
62
63
/**
64
* Check if rule is deprecated
65
* @return true if rule is marked as deprecated
66
*/
67
boolean isDeprecated();
68
69
/**
70
* Set deprecation status
71
* @param deprecated true to mark rule as deprecated
72
*/
73
void setDeprecated(boolean deprecated);
74
75
/**
76
* Get rule name (unique identifier within ruleset)
77
* @return Name of the rule
78
*/
79
String getName();
80
81
/**
82
* Set rule name
83
* @param name Unique name for the rule
84
*/
85
void setName(String name);
86
87
/**
88
* Get PMD version when rule was added
89
* @return Version string when rule was introduced
90
*/
91
String getSince();
92
93
/**
94
* Set PMD version when rule was added
95
* @param since Version string when rule was introduced
96
*/
97
void setSince(String since);
98
99
/**
100
* Get rule implementation class name
101
* @return Fully qualified class name of rule implementation
102
*/
103
String getRuleClass();
104
105
/**
106
* Set rule implementation class name
107
* @param ruleClass Fully qualified class name
108
*/
109
void setRuleClass(String ruleClass);
110
111
/**
112
* Get violation message template
113
* @return Message template for violations (may contain {0} placeholders)
114
*/
115
String getMessage();
116
117
/**
118
* Set violation message template
119
* @param message Template for violation messages
120
*/
121
void setMessage(String message);
122
123
/**
124
* Get rule description
125
* @return Detailed description of what rule checks
126
*/
127
String getDescription();
128
129
/**
130
* Set rule description
131
* @param description Detailed description of rule purpose
132
*/
133
void setDescription(String description);
134
135
/**
136
* Get external documentation URL
137
* @return URL to external documentation, or null
138
*/
139
String getExternalInfoUrl();
140
141
/**
142
* Set external documentation URL
143
* @param externalInfoUrl URL to external documentation
144
*/
145
void setExternalInfoUrl(String externalInfoUrl);
146
147
/**
148
* Get rule priority level
149
* @return RulePriority indicating severity/importance
150
*/
151
RulePriority getPriority();
152
153
/**
154
* Set rule priority level
155
* @param priority RulePriority for violation severity
156
*/
157
void setPriority(RulePriority priority);
158
159
/**
160
* Check if rule uses Data Flow Analysis (DFA)
161
* @return true if rule requires DFA processing
162
*/
163
boolean isDfa();
164
165
/**
166
* Enable/disable Data Flow Analysis
167
* @param dfa true to enable DFA processing for this rule
168
*/
169
void setDfa(boolean dfa);
170
171
/**
172
* Check if rule uses type resolution
173
* @return true if rule requires type information
174
*/
175
boolean isTypeResolution();
176
177
/**
178
* Enable/disable type resolution
179
* @param typeResolution true to enable type resolution for this rule
180
*/
181
void setTypeResolution(boolean typeResolution);
182
183
/**
184
* Check if rule processes multiple files
185
* @return true if rule analyzes across multiple files
186
*/
187
boolean usesMultifile();
188
189
/**
190
* Set multifile processing capability
191
* @param multifile true if rule should process multiple files
192
*/
193
void setMultifile(boolean multifile);
194
195
/**
196
* Create deep copy of rule with all properties
197
* @return Independent copy of this rule
198
*/
199
Rule deepCopy();
200
201
/**
202
* Apply rule to AST node within rule context
203
* @param target AST node to analyze
204
* @param ctx RuleContext for violation reporting
205
*/
206
void apply(Node target, RuleContext ctx);
207
}
208
```
209
210
**Usage Examples:**
211
212
```java
213
import net.sourceforge.pmd.lang.rule.*;
214
import net.sourceforge.pmd.lang.ast.Node;
215
import net.sourceforge.pmd.properties.*;
216
217
// Example custom rule implementation
218
public class MyCustomRule extends AbstractRule {
219
220
private static final PropertyDescriptor<Integer> THRESHOLD_DESCRIPTOR =
221
PropertyFactory.intProperty("threshold")
222
.desc("Maximum allowed complexity")
223
.defaultValue(10)
224
.build();
225
226
public MyCustomRule() {
227
definePropertyDescriptor(THRESHOLD_DESCRIPTOR);
228
setName("MyCustomRule");
229
setMessage("Complexity {0} exceeds threshold {1}");
230
setDescription("Detects overly complex code structures");
231
setPriority(RulePriority.MEDIUM);
232
setLanguage(LanguageRegistry.getLanguage("java"));
233
}
234
235
@Override
236
public void apply(Node target, RuleContext ctx) {
237
int threshold = getProperty(THRESHOLD_DESCRIPTOR);
238
239
int complexity = calculateComplexity(target);
240
if (complexity > threshold) {
241
ctx.addViolation(target, complexity, threshold);
242
}
243
}
244
245
private int calculateComplexity(Node node) {
246
// Custom complexity calculation logic
247
return node.getNumChildren() + 1;
248
}
249
}
250
251
// Configuring rule properties programmatically
252
Rule rule = new MyCustomRule();
253
rule.setProperty(MyCustomRule.THRESHOLD_DESCRIPTOR, 15);
254
rule.setPriority(RulePriority.HIGH);
255
rule.setMessage("Custom message: complexity {0} is too high");
256
257
// Creating rule copy with modifications
258
Rule ruleCopy = rule.deepCopy();
259
ruleCopy.setName("MyCustomRuleVariant");
260
ruleCopy.setPriority(RulePriority.LOW);
261
```
262
263
### RuleSet Collection
264
265
Collection of rules with optional file filtering patterns and metadata management.
266
267
```java { .api }
268
/**
269
* Collection of rules with optional file filtering patterns.
270
* Implements ChecksumAware for caching support.
271
*/
272
public class RuleSet implements ChecksumAware {
273
274
/**
275
* Copy constructor
276
* @param rs RuleSet to copy
277
*/
278
RuleSet(RuleSet rs);
279
280
/**
281
* Create ruleset containing single rule
282
* @param rule Single rule for the ruleset
283
* @return RuleSet containing only the specified rule
284
*/
285
static RuleSet forSingleRule(Rule rule);
286
287
/**
288
* Create new ruleset builder for programmatic construction
289
* @return RuleSetBuilder for fluent ruleset creation
290
*/
291
static RuleSetBuilder create();
292
293
/**
294
* Get source filename where ruleset was defined
295
* @return Filename of ruleset definition, or null if programmatic
296
*/
297
String getFileName();
298
299
/**
300
* Get ruleset name
301
* @return Name of the ruleset
302
*/
303
String getName();
304
305
/**
306
* Get ruleset description
307
* @return Description of ruleset purpose and contents
308
*/
309
String getDescription();
310
311
/**
312
* Get all rules in set
313
* @return Unmodifiable list of rules
314
*/
315
List<Rule> getRules();
316
317
/**
318
* Get rule by name
319
* @param ruleName Name of rule to find
320
* @return Rule with matching name, or null if not found
321
*/
322
Rule getRuleByName(String ruleName);
323
324
/**
325
* Check if ruleset applies to specific file
326
* @param file FileId to check against include/exclude patterns
327
* @return true if ruleset should be applied to file
328
*/
329
boolean applies(FileId file);
330
331
/**
332
* Check if ruleset applies to language version
333
* @param languageVersion LanguageVersion to check compatibility
334
* @return true if ruleset contains rules for language version
335
*/
336
boolean applies(LanguageVersion languageVersion);
337
338
/**
339
* Get checksum for caching and change detection
340
* @return Checksum representing current ruleset state
341
*/
342
long getChecksum();
343
344
/**
345
* Get number of rules in set
346
* @return Count of rules
347
*/
348
int size();
349
350
/**
351
* Iterate over rules in set
352
* @return Iterator for rules
353
*/
354
Iterator<Rule> iterator();
355
}
356
```
357
358
**Usage Examples:**
359
360
```java
361
import net.sourceforge.pmd.lang.rule.*;
362
import java.util.Arrays;
363
364
// Creating ruleset from single rule
365
Rule customRule = new MyCustomRule();
366
RuleSet singleRuleSet = RuleSet.forSingleRule(customRule);
367
368
// Building ruleset programmatically
369
RuleSet programmaticRuleSet = RuleSet.create()
370
.withName("Custom Analysis Rules")
371
.withDescription("Rules for custom static analysis")
372
.addRule(new MyCustomRule())
373
.addRule(new AnotherCustomRule())
374
.build();
375
376
// Working with loaded ruleset
377
RuleSetLoader loader = analysis.newRuleSetLoader();
378
RuleSet javaRules = loader.loadFromResource("rulesets/java/quickstart.xml");
379
380
System.out.printf("Loaded %d rules from %s%n",
381
javaRules.size(),
382
javaRules.getName());
383
384
// Check rule applicability
385
FileId javaFile = FileId.fromPathLikeString("src/main/java/Example.java");
386
if (javaRules.applies(javaFile)) {
387
System.out.println("Ruleset applies to Java files");
388
}
389
390
// Find specific rule
391
Rule specificRule = javaRules.getRuleByName("UnusedPrivateField");
392
if (specificRule != null) {
393
System.out.printf("Found rule: %s - %s%n",
394
specificRule.getName(),
395
specificRule.getDescription());
396
}
397
398
// Iterate through rules
399
for (Rule rule : javaRules) {
400
System.out.printf("Rule: %s (Priority: %s)%n",
401
rule.getName(),
402
rule.getPriority().getName());
403
}
404
```
405
406
### RuleSet Loading
407
408
Comprehensive ruleset loading from various sources including files, resources, URLs, and programmatic construction.
409
410
```java { .api }
411
/**
412
* Loads rulesets from various sources with filtering and configuration options.
413
*/
414
public final class RuleSetLoader {
415
416
/**
417
* Create RuleSetLoader from PMD configuration
418
* @param configuration PMDConfiguration with loading settings
419
* @return Configured RuleSetLoader instance
420
*/
421
static RuleSetLoader fromPmdConfig(PMDConfiguration configuration);
422
423
/**
424
* Load ruleset from resource path, file, or URL
425
* @param ruleSetReferenceId Resource path, file path, or URL to ruleset
426
* @return Loaded RuleSet with all rules and configurations
427
* @throws RuleSetLoadException if loading fails
428
*/
429
RuleSet loadFromResource(String ruleSetReferenceId);
430
431
/**
432
* Load multiple rulesets from list of references
433
* @param ruleSetReferenceIds List of resource paths, file paths, or URLs
434
* @return List of loaded RuleSet instances
435
* @throws RuleSetLoadException if any loading fails
436
*/
437
List<RuleSet> loadFromResources(List<String> ruleSetReferenceIds);
438
439
/**
440
* Create new ruleset builder for programmatic construction
441
* @return RuleSetBuilder for creating custom rulesets
442
*/
443
RuleSetBuilder newRuleSetBuilder();
444
445
/**
446
* Enable compatibility mode for legacy rulesets
447
* @param enable true to enable compatibility processing
448
* @return Previous compatibility mode setting
449
*/
450
boolean enableCompatibility(boolean enable);
451
452
/**
453
* Create filtered loader that only loads rules above priority threshold
454
* @param minimumPriority Minimum RulePriority to include
455
* @return New RuleSetLoader with priority filtering
456
*/
457
RuleSetLoader filterAbovePriority(RulePriority minimumPriority);
458
459
/**
460
* Get exceptions that occurred during loading
461
* @return List of RuleSetLoadException for failed loads
462
*/
463
List<RuleSetLoadException> getLoadExceptions();
464
}
465
```
466
467
**Usage Examples:**
468
469
```java
470
import net.sourceforge.pmd.lang.rule.*;
471
import java.util.Arrays;
472
import java.util.List;
473
474
// Create loader from configuration
475
PMDConfiguration config = new PMDConfiguration();
476
RuleSetLoader loader = RuleSetLoader.fromPmdConfig(config);
477
478
// Load single ruleset
479
try {
480
RuleSet javaQuickstart = loader.loadFromResource("rulesets/java/quickstart.xml");
481
System.out.printf("Loaded %d rules%n", javaQuickstart.size());
482
} catch (RuleSetLoadException e) {
483
System.err.println("Failed to load ruleset: " + e.getMessage());
484
}
485
486
// Load multiple rulesets
487
List<String> rulesetPaths = Arrays.asList(
488
"rulesets/java/quickstart.xml",
489
"rulesets/java/design.xml",
490
"rulesets/java/errorprone.xml",
491
"custom-rules.xml",
492
"https://example.com/remote-rules.xml"
493
);
494
495
try {
496
List<RuleSet> ruleSets = loader.loadFromResources(rulesetPaths);
497
int totalRules = ruleSets.stream().mapToInt(RuleSet::size).sum();
498
System.out.printf("Loaded %d rulesets with %d total rules%n",
499
ruleSets.size(), totalRules);
500
} catch (RuleSetLoadException e) {
501
System.err.println("Failed to load some rulesets: " + e.getMessage());
502
}
503
504
// Filter rules by priority
505
RuleSetLoader highPriorityLoader = loader.filterAbovePriority(RulePriority.MEDIUM);
506
RuleSet filteredRules = highPriorityLoader.loadFromResource("rulesets/java/quickstart.xml");
507
508
// Check for loading exceptions
509
List<RuleSetLoadException> exceptions = loader.getLoadExceptions();
510
if (!exceptions.isEmpty()) {
511
System.err.println("Loading issues occurred:");
512
for (RuleSetLoadException exception : exceptions) {
513
System.err.printf(" %s: %s%n", exception.getLocation(), exception.getMessage());
514
}
515
}
516
517
// Build custom ruleset
518
RuleSetBuilder builder = loader.newRuleSetBuilder();
519
RuleSet customRuleSet = builder
520
.withName("Custom Project Rules")
521
.withDescription("Rules specific to our project")
522
.addRule(new MyCustomRule())
523
.addRuleByReference("rulesets/java/design.xml/CyclomaticComplexity")
524
.build();
525
```
526
527
### Rule Priority System
528
529
Enumeration defining rule priority levels from HIGH to LOW with utility methods for comparison and conversion.
530
531
```java { .api }
532
/**
533
* Rule priority levels from HIGH (most important) to LOW (least important).
534
* Used for filtering rules and determining violation severity.
535
*/
536
public enum RulePriority {
537
538
/** Highest priority - critical issues */
539
HIGH(1),
540
541
/** Medium-high priority - important issues */
542
MEDIUM_HIGH(2),
543
544
/** Medium priority - standard issues */
545
MEDIUM(3),
546
547
/** Medium-low priority - minor issues */
548
MEDIUM_LOW(4),
549
550
/** Lowest priority - informational issues */
551
LOW(5);
552
553
/**
554
* Get numeric priority value (lower numbers = higher priority)
555
* @return Numeric priority (1-5)
556
*/
557
int getPriority();
558
559
/**
560
* Get priority name as string
561
* @return String representation of priority level
562
*/
563
String getName();
564
565
/**
566
* Get RulePriority by numeric value
567
* @param priority Numeric priority (1-5)
568
* @return RulePriority corresponding to numeric value
569
* @throws IllegalArgumentException if priority not in valid range
570
*/
571
static RulePriority valueOf(int priority);
572
573
/**
574
* Get RulePriority by name (case-insensitive)
575
* @param name Priority name (HIGH, MEDIUM, LOW, etc.)
576
* @return RulePriority corresponding to name
577
* @throws IllegalArgumentException if name not recognized
578
*/
579
static RulePriority valueOfName(String name);
580
}
581
```
582
583
**Usage Examples:**
584
585
```java
586
import net.sourceforge.pmd.lang.rule.RulePriority;
587
588
// Working with rule priorities
589
RulePriority high = RulePriority.HIGH;
590
System.out.printf("Priority %s has numeric value %d%n",
591
high.getName(), high.getPriority());
592
593
// Creating priorities from values
594
RulePriority medium = RulePriority.valueOf(3);
595
RulePriority low = RulePriority.valueOfName("LOW");
596
597
// Comparing priorities
598
if (RulePriority.HIGH.getPriority() < RulePriority.LOW.getPriority()) {
599
System.out.println("HIGH priority has lower numeric value than LOW");
600
}
601
602
// Using priorities in rule configuration
603
Rule rule = new MyCustomRule();
604
rule.setPriority(RulePriority.MEDIUM_HIGH);
605
606
// Filtering by priority in configuration
607
PMDConfiguration config = new PMDConfiguration();
608
config.setMinimumPriority(RulePriority.MEDIUM); // Only MEDIUM+ priority rules
609
610
// Priority-based rule filtering
611
RuleSetLoader loader = RuleSetLoader.fromPmdConfig(config);
612
RuleSetLoader highPriorityLoader = loader.filterAbovePriority(RulePriority.MEDIUM_HIGH);
613
614
// Setting priorities programmatically for different environments
615
if (isProductionBuild()) {
616
config.setMinimumPriority(RulePriority.HIGH);
617
} else if (isDevelopmentBuild()) {
618
config.setMinimumPriority(RulePriority.LOW);
619
} else {
620
config.setMinimumPriority(RulePriority.MEDIUM);
621
}
622
```
623
624
## Types
625
626
```java { .api }
627
/**
628
* Builder for constructing RuleSet instances programmatically
629
*/
630
interface RuleSetBuilder {
631
RuleSetBuilder withName(String name);
632
RuleSetBuilder withDescription(String description);
633
RuleSetBuilder addRule(Rule rule);
634
RuleSetBuilder addRuleByReference(String ruleReference);
635
RuleSetBuilder filterRules(Predicate<Rule> filter);
636
RuleSet build();
637
}
638
639
/**
640
* Context for rule execution providing violation reporting capabilities
641
*/
642
interface RuleContext {
643
void addViolation(Node node);
644
void addViolation(Node node, Object... args);
645
void addViolationWithMessage(Node node, String message);
646
LanguageVersion getLanguageVersion();
647
String getFilename();
648
TextDocument getTextDocument();
649
boolean isIgnored(Rule rule);
650
}
651
652
/**
653
* Exception thrown when ruleset loading fails
654
*/
655
class RuleSetLoadException extends Exception {
656
String getLocation();
657
String getCause();
658
List<String> getErrors();
659
}
660
661
/**
662
* Interface for objects that can compute checksums for caching
663
*/
664
interface ChecksumAware {
665
long getChecksum();
666
}
667
668
/**
669
* File identifier for tracking files across analysis
670
*/
671
interface FileId {
672
String getAbsolutePath();
673
String getDisplayName();
674
URI getUri();
675
static FileId fromPathLikeString(String pathLike);
676
static FileId fromPath(Path path);
677
}
678
679
/**
680
* Language version representing specific version of programming language
681
*/
682
interface LanguageVersion extends Comparable<LanguageVersion> {
683
Language getLanguage();
684
String getVersion();
685
String getName();
686
String getShortName();
687
String getTerseName();
688
}
689
690
/**
691
* Abstract base class for rule implementations
692
*/
693
abstract class AbstractRule implements Rule {
694
protected void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptor);
695
protected void addRuleChainVisit(String astNodeName);
696
protected void addRuleChainVisit(Class<? extends Node> nodeType);
697
}
698
```