0
# Rule Development Framework
1
2
Base classes and framework for creating custom PMD rules. Includes abstract base rule class with visitor pattern integration and comprehensive set of built-in rules across 6 categories.
3
4
## Capabilities
5
6
### Base Rule Class
7
8
Abstract base class for all Apex PMD rules with visitor pattern integration.
9
10
```java { .api }
11
/**
12
* Base class for all Apex PMD rules
13
* Implements ApexVisitor for AST traversal and rule logic
14
*/
15
public abstract class AbstractApexRule implements ApexVisitor<Object, Object> {
16
/**
17
* Apply rule to a node with rule context
18
* @param target - AST node to analyze
19
* @param ctx - Rule context with violation reporting capabilities
20
*/
21
public void apply(Node target, RuleContext ctx);
22
23
// Inherits all 80+ visit methods from ApexVisitor interface
24
// Override specific visit methods to implement rule logic
25
Object visit(ASTUserClass node, Object data);
26
Object visit(ASTMethod node, Object data);
27
Object visit(ASTVariableExpression node, Object data);
28
// ... and 80+ more visit methods
29
}
30
```
31
32
### Rule Context
33
34
Context object providing violation reporting and analysis capabilities.
35
36
```java { .api }
37
/**
38
* Rule execution context
39
*/
40
public interface RuleContext {
41
/** Report a violation at the given node */
42
void addViolation(Node node, String message);
43
44
/** Report a violation with custom message parameters */
45
void addViolation(Node node, String message, Object... params);
46
47
/** Get current file name being analyzed */
48
String getFileDisplayName();
49
50
/** Get language processor for multifile analysis */
51
LanguageProcessor getLanguageProcessor();
52
53
/** Get rule being executed */
54
Rule getRule();
55
}
56
```
57
58
## Built-in Rules by Category
59
60
### Best Practices Rules
61
62
Rules promoting Apex coding best practices and testing standards.
63
64
```java { .api }
65
/**
66
* Base class for unit test related rules
67
*/
68
public abstract class AbstractApexUnitTestRule extends AbstractApexRule {
69
/** Check if class is a test class */
70
protected boolean isTestClass(ASTUserClass node);
71
}
72
73
/**
74
* Assertions should include descriptive messages
75
*/
76
public class ApexAssertionsShouldIncludeMessageRule extends AbstractApexRule {
77
// Checks System.assert() calls for message parameters
78
}
79
80
/**
81
* Test classes should contain assertions
82
*/
83
public class ApexUnitTestClassShouldHaveAssertsRule extends AbstractApexUnitTestRule {
84
// Verifies test methods contain assertion statements
85
}
86
87
/**
88
* Test classes should use RunAs for proper test isolation
89
*/
90
public class ApexUnitTestClassShouldHaveRunAsRule extends AbstractApexUnitTestRule {
91
// Checks for System.runAs() usage in test methods
92
}
93
94
/**
95
* Avoid SeeAllData=true in test classes
96
*/
97
public class ApexUnitTestShouldNotUseSeeAllDataTrueRule extends AbstractApexUnitTestRule {
98
// Detects @isTest(SeeAllData=true) annotations
99
}
100
101
/**
102
* Avoid using global modifier unnecessarily
103
*/
104
public class AvoidGlobalModifierRule extends AbstractApexRule {
105
// Flags unnecessary global access modifiers
106
}
107
108
/**
109
* Keep trigger logic simple, delegate to handler classes
110
*/
111
public class AvoidLogicInTriggerRule extends AbstractApexRule {
112
// Detects complex logic directly in trigger bodies
113
}
114
115
/**
116
* Queueable jobs should implement finalizer methods
117
*/
118
public class QueueableWithoutFinalizerRule extends AbstractApexRule {
119
// Checks Queueable classes for proper error handling
120
}
121
122
/**
123
* Detect unused local variables
124
*/
125
public class UnusedLocalVariableRule extends AbstractApexRule {
126
// Identifies local variables that are declared but never used
127
}
128
```
129
130
### Code Style Rules
131
132
Rules enforcing naming conventions and code organization.
133
134
```java { .api }
135
/**
136
* Base class for naming convention rules
137
*/
138
public abstract class AbstractNamingConventionsRule extends AbstractApexRule {
139
/** Check if name matches expected pattern */
140
protected boolean matches(String name, Pattern pattern);
141
}
142
143
/**
144
* Enforce class naming conventions
145
*/
146
public class ClassNamingConventionsRule extends AbstractNamingConventionsRule {
147
// Enforces PascalCase for class names
148
}
149
150
/**
151
* Fields should be declared at the start of classes
152
*/
153
public class FieldDeclarationsShouldBeAtStartRule extends AbstractApexRule {
154
// Checks field placement within class body
155
}
156
157
/**
158
* Enforce field naming conventions
159
*/
160
public class FieldNamingConventionsRule extends AbstractNamingConventionsRule {
161
// Enforces camelCase for field names
162
}
163
164
/**
165
* Enforce method parameter naming conventions
166
*/
167
public class FormalParameterNamingConventionsRule extends AbstractNamingConventionsRule {
168
// Enforces camelCase for parameter names
169
}
170
171
/**
172
* Enforce local variable naming conventions
173
*/
174
public class LocalVariableNamingConventionsRule extends AbstractNamingConventionsRule {
175
// Enforces camelCase for local variable names
176
}
177
178
/**
179
* Enforce method naming conventions
180
*/
181
public class MethodNamingConventionsRule extends AbstractNamingConventionsRule {
182
// Enforces camelCase for method names
183
}
184
185
/**
186
* Enforce property naming conventions
187
*/
188
public class PropertyNamingConventionsRule extends AbstractNamingConventionsRule {
189
// Enforces PascalCase for property names
190
}
191
```
192
193
### Design Rules
194
195
Rules measuring code complexity and design quality.
196
197
```java { .api }
198
/**
199
* Base class for NCSS (Non-Commenting Source Statements) counting rules
200
*/
201
public abstract class AbstractNcssCountRule extends AbstractApexRule {
202
/** Count NCSS for a node */
203
protected int countNcss(Node node);
204
}
205
206
/**
207
* Avoid deeply nested if statements
208
*/
209
public class AvoidDeeplyNestedIfStmtsRule extends AbstractApexRule {
210
// Detects excessive nesting depth in conditional statements
211
}
212
213
/**
214
* Measure cognitive complexity of methods
215
*/
216
public class CognitiveComplexityRule extends AbstractApexRule {
217
// Uses ApexMetrics.COGNITIVE_COMPLEXITY to measure understandability
218
}
219
220
/**
221
* Measure cyclomatic complexity of methods
222
*/
223
public class CyclomaticComplexityRule extends AbstractApexRule {
224
// Uses ApexMetrics.CYCLO to measure code paths
225
}
226
227
/**
228
* Limit class length
229
*/
230
public class ExcessiveClassLengthRule extends AbstractApexRule {
231
// Counts lines of code in class declarations
232
}
233
234
/**
235
* Limit method parameter count
236
*/
237
public class ExcessiveParameterListRule extends AbstractApexRule {
238
// Counts parameters in method signatures
239
}
240
241
/**
242
* Limit number of public members in classes
243
*/
244
public class ExcessivePublicCountRule extends AbstractApexRule {
245
// Counts public methods, fields, and properties
246
}
247
248
/**
249
* Measure constructor complexity using NCSS
250
*/
251
public class NcssConstructorCountRule extends AbstractNcssCountRule {
252
// Applies NCSS counting to constructor methods
253
}
254
255
/**
256
* Measure method complexity using NCSS
257
*/
258
public class NcssMethodCountRule extends AbstractNcssCountRule {
259
// Applies NCSS counting to regular methods
260
}
261
262
/**
263
* Measure type complexity using NCSS
264
*/
265
public class NcssTypeCountRule extends AbstractNcssCountRule {
266
// Applies NCSS counting to entire classes/interfaces
267
}
268
269
/**
270
* Standard cyclomatic complexity measurement
271
*/
272
public class StdCyclomaticComplexityRule extends AbstractApexRule {
273
// Standard implementation of cyclomatic complexity
274
}
275
276
/**
277
* Limit number of fields in classes
278
*/
279
public class TooManyFieldsRule extends AbstractApexRule {
280
// Counts field declarations in classes
281
}
282
283
/**
284
* Detect unused methods
285
*/
286
public class UnusedMethodRule extends AbstractApexRule {
287
// Identifies methods that are declared but never called
288
}
289
```
290
291
### Error Prone Rules
292
293
Rules detecting common programming errors and potential bugs.
294
295
```java { .api }
296
/**
297
* Detect CSRF vulnerabilities in Apex
298
*/
299
public class ApexCSRFRule extends AbstractApexRule {
300
// Identifies potential Cross-Site Request Forgery issues
301
}
302
303
/**
304
* Avoid hardcoded Salesforce record IDs
305
*/
306
public class AvoidHardcodingIdRule extends AbstractApexRule {
307
// Detects hardcoded 15 or 18 character Salesforce IDs
308
}
309
310
/**
311
* Check for non-existent annotations
312
*/
313
public class AvoidNonExistentAnnotationsRule extends AbstractApexRule {
314
// Validates annotation usage against known Apex annotations
315
}
316
317
/**
318
* Avoid stateful database operations that can cause issues
319
*/
320
public class AvoidStatefulDatabaseResultRule extends AbstractApexRule {
321
// Detects problematic database result handling patterns
322
}
323
324
/**
325
* Check Aura-enabled getter accessibility
326
*/
327
public class InaccessibleAuraEnabledGetterRule extends AbstractApexRule {
328
// Validates @AuraEnabled getter method accessibility
329
}
330
331
/**
332
* Method names should not match enclosing class name
333
*/
334
public class MethodWithSameNameAsEnclosingClassRule extends AbstractApexRule {
335
// Detects methods that should be constructors
336
}
337
338
/**
339
* Override both equals and hashCode together
340
*/
341
public class OverrideBothEqualsAndHashcodeRule extends AbstractApexRule {
342
// Ensures both methods are implemented when one is overridden
343
}
344
345
/**
346
* Avoid shadowing built-in namespace names
347
*/
348
public class TypeShadowsBuiltInNamespaceRule extends AbstractApexRule {
349
// Detects types that shadow Salesforce built-in namespaces
350
}
351
```
352
353
### Performance Rules
354
355
Rules identifying performance bottlenecks and optimization opportunities.
356
357
```java { .api }
358
/**
359
* Base class for rules detecting expensive operations in loops
360
*/
361
public abstract class AbstractAvoidNodeInLoopsRule extends AbstractApexRule {
362
/** Check if node is inside a loop construct */
363
protected boolean isInLoop(Node node);
364
}
365
366
/**
367
* Avoid non-selective SOQL queries
368
*/
369
public class AvoidNonRestrictiveQueriesRule extends AbstractApexRule {
370
// Detects SOQL queries without WHERE clauses or selective filters
371
}
372
373
/**
374
* Avoid expensive operations inside loops
375
*/
376
public class OperationWithHighCostInLoopRule extends AbstractAvoidNodeInLoopsRule {
377
// Detects DML, SOQL, and other expensive operations in loops
378
}
379
380
/**
381
* Avoid operations that consume governor limits inside loops
382
*/
383
public class OperationWithLimitsInLoopRule extends AbstractAvoidNodeInLoopsRule {
384
// Identifies operations that can hit Salesforce governor limits
385
}
386
```
387
388
### Security Rules
389
390
Rules detecting security vulnerabilities and unsafe practices.
391
392
```java { .api }
393
/**
394
* Detect bad cryptographic practices
395
*/
396
public class ApexBadCryptoRule extends AbstractApexRule {
397
// Identifies weak encryption algorithms and practices
398
}
399
400
/**
401
* Detect CRUD and FLS security violations
402
*/
403
public class ApexCRUDViolationRule extends AbstractApexRule {
404
// Checks for proper CRUD/FLS security enforcement
405
}
406
407
/**
408
* Detect usage of dangerous methods
409
*/
410
public class ApexDangerousMethodsRule extends AbstractApexRule {
411
// Identifies potentially unsafe method calls
412
}
413
414
/**
415
* Detect insecure HTTP endpoints
416
*/
417
public class ApexInsecureEndpointRule extends AbstractApexRule {
418
// Checks for HTTP instead of HTTPS endpoints
419
}
420
421
/**
422
* Detect open redirect vulnerabilities
423
*/
424
public class ApexOpenRedirectRule extends AbstractApexRule {
425
// Identifies potential open redirect vulnerabilities
426
}
427
428
/**
429
* Detect SOQL injection vulnerabilities
430
*/
431
public class ApexSOQLInjectionRule extends AbstractApexRule {
432
// Checks for dynamic SOQL construction without proper sanitization
433
}
434
435
/**
436
* Detect sharing rule violations
437
*/
438
public class ApexSharingViolationsRule extends AbstractApexRule {
439
// Identifies bypass of Salesforce sharing rules
440
}
441
442
/**
443
* Suggest using Named Credentials for external calls
444
*/
445
public class ApexSuggestUsingNamedCredRule extends AbstractApexRule {
446
// Recommends Named Credentials over hardcoded endpoints
447
}
448
449
/**
450
* Detect XSS vulnerabilities from escape=false
451
*/
452
public class ApexXSSFromEscapeFalseRule extends AbstractApexRule {
453
// Identifies potential XSS from unescaped output
454
}
455
456
/**
457
* Detect XSS vulnerabilities from URL parameters
458
*/
459
public class ApexXSSFromURLParamRule extends AbstractApexRule {
460
// Checks for unsafe URL parameter handling
461
}
462
```
463
464
**Usage Examples:**
465
466
```java
467
// Create custom rule extending AbstractApexRule
468
public class CustomComplexityRule extends AbstractApexRule {
469
@Override
470
public Object visit(ASTMethod node, Object data) {
471
RuleContext ctx = (RuleContext) data;
472
473
// Use metrics to check complexity
474
int complexity = MetricsUtil.computeMetric(ApexMetrics.CYCLO, node);
475
if (complexity > 15) {
476
ctx.addViolation(node, "Method complexity too high: {0}", complexity);
477
}
478
479
// Check for specific patterns
480
List<ASTSoqlExpression> queries = node.findDescendantsOfType(ASTSoqlExpression.class);
481
if (queries.size() > 3) {
482
ctx.addViolation(node, "Too many SOQL queries in method: {0}", queries.size());
483
}
484
485
return super.visit(node, data);
486
}
487
488
@Override
489
public Object visit(ASTUserClass node, Object data) {
490
RuleContext ctx = (RuleContext) data;
491
492
// Check class naming
493
String className = node.getQualifiedName().getClassName();
494
if (!className.matches("^[A-Z][a-zA-Z0-9]*$")) {
495
ctx.addViolation(node, "Class name should follow PascalCase convention");
496
}
497
498
// Use multifile analysis if available
499
ApexMultifileAnalysis multifile = ctx.getLanguageProcessor().getMultiFileState();
500
if (!multifile.isFailed()) {
501
List<Issue> issues = multifile.getFileIssues(ctx.getFileDisplayName());
502
for (Issue issue : issues) {
503
if (issue.getSeverity() == Severity.ERROR) {
504
ctx.addViolation(node, "Multifile analysis error: {0}", issue.getMessage());
505
}
506
}
507
}
508
509
return super.visit(node, data);
510
}
511
}
512
513
// Rule that checks for test class patterns
514
public class TestClassValidationRule extends AbstractApexUnitTestRule {
515
@Override
516
public Object visit(ASTUserClass node, Object data) {
517
if (isTestClass(node)) {
518
RuleContext ctx = (RuleContext) data;
519
520
// Check for test methods
521
List<ASTMethod> methods = node.findDescendantsOfType(ASTMethod.class);
522
boolean hasTestMethods = methods.stream()
523
.anyMatch(method -> method.getName().toLowerCase().contains("test"));
524
525
if (!hasTestMethods) {
526
ctx.addViolation(node, "Test class should contain test methods");
527
}
528
529
// Check for assertions in test methods
530
for (ASTMethod method : methods) {
531
if (method.getName().toLowerCase().contains("test")) {
532
List<ASTMethodCallExpression> calls = method.findDescendantsOfType(ASTMethodCallExpression.class);
533
boolean hasAssertions = calls.stream()
534
.anyMatch(call -> call.getMethodName().startsWith("assert"));
535
536
if (!hasAssertions) {
537
ctx.addViolation(method, "Test method should contain assertions");
538
}
539
}
540
}
541
}
542
543
return super.visit(node, data);
544
}
545
}
546
```