test-pmd-rule 0.1.0 โ 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +232 -67
- package/dist/test-pmd-rule.js +45 -3
- package/dist/test-pmd-rule.js.map +2 -2
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# test-pmd-rule
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A high-performance PMD Rule Tester for Apex rules with directory support, parallel execution, XPath analysis, and LCOV coverage reporting.
|
|
4
4
|
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
@@ -10,57 +10,257 @@ A standalone PMD Rule Tester for Apex rules, with XPath analysis and coverage va
|
|
|
10
10
|
|
|
11
11
|
This tool validates PMD Apex rules by testing them against examples embedded in the rule XML files. It ensures rules work correctly, provide adequate test coverage, and don't contain hardcoded values that should be parameterized.
|
|
12
12
|
|
|
13
|
+
**Key Capabilities:**
|
|
14
|
+
|
|
15
|
+
- **Mass Testing**: Test entire directories of rule files with recursive discovery
|
|
16
|
+
- **High Performance**: CPU-core-based parallel execution for blazing-fast testing
|
|
17
|
+
- **Coverage Analysis**: Generate LCOV coverage reports for XPath expressions
|
|
18
|
+
- **Comprehensive Validation**: Full XPath analysis with line number tracking
|
|
19
|
+
|
|
20
|
+
This project was born out of necessity as hood tooling for both humans and AI Agents was essential for furthering the [sca-extra](https://github.com/starch-uk/sca-extra) project.
|
|
21
|
+
|
|
22
|
+
### Output
|
|
23
|
+
|
|
24
|
+
The tool provides detailed output including:
|
|
25
|
+
|
|
26
|
+
- **Test Details**: Individual test results for each example (violation/valid tests)
|
|
27
|
+
- **Test Summary**: Overall statistics (examples tested, passed, total violations)
|
|
28
|
+
- **XPath Coverage**: Detailed coverage analysis showing:
|
|
29
|
+
- Node types coverage with line numbers for missing items
|
|
30
|
+
- Conditionals coverage (only missing items shown)
|
|
31
|
+
- Attributes coverage with line numbers for missing items
|
|
32
|
+
- Operators coverage
|
|
33
|
+
- **Final Status**: Overall pass/fail status
|
|
34
|
+
|
|
35
|
+
Example output (single file):
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
๐งช Testing rule: rulesets/code-style/MyRule.xml
|
|
39
|
+
|
|
40
|
+
๐ Test Details:
|
|
41
|
+
- Example 1 Test: Violation โ
|
|
42
|
+
- Example 1 Test: Valid โ
|
|
43
|
+
- Example 2 Test: Violation โ
|
|
44
|
+
|
|
45
|
+
๐ Test Summary:
|
|
46
|
+
Examples tested: 2
|
|
47
|
+
Examples passed: 2
|
|
48
|
+
Total violations: 3
|
|
49
|
+
Rule triggers violations: โ
Yes
|
|
50
|
+
|
|
51
|
+
๐ XPath Coverage:
|
|
52
|
+
Status: โ ๏ธ Incomplete
|
|
53
|
+
Coverage items: 2
|
|
54
|
+
1. โ ๏ธ Node types: 4/6 covered
|
|
55
|
+
Missing:
|
|
56
|
+
- Line 43: VariableDeclaration
|
|
57
|
+
- Line 50: VariableExpression
|
|
58
|
+
2. โ ๏ธ Attributes: 2/3 covered
|
|
59
|
+
Missing:
|
|
60
|
+
- Line 52: BeginLine
|
|
61
|
+
|
|
62
|
+
โ
All tests passed!
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Example output (directory with parallel execution):
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
๐ Processing 15 rule file(s) with 8 parallel workers
|
|
69
|
+
Each file will test examples with up to 16 parallel workers
|
|
70
|
+
|
|
71
|
+
๐งช Testing rule: rulesets/code-style/MyRule.xml
|
|
72
|
+
|
|
73
|
+
๐ Test Details:
|
|
74
|
+
- Example 1 Test: Violation โ
|
|
75
|
+
- Example 1 Test: Valid โ
|
|
76
|
+
- Example 2 Test: Violation โ
|
|
77
|
+
|
|
78
|
+
๐ Test Summary:
|
|
79
|
+
Examples tested: 2
|
|
80
|
+
Examples passed: 2
|
|
81
|
+
Total violations: 3
|
|
82
|
+
Rule triggers violations: โ
Yes
|
|
83
|
+
|
|
84
|
+
๐ XPath Coverage:
|
|
85
|
+
Status: โ
Complete
|
|
86
|
+
|
|
87
|
+
โ
All tests passed!
|
|
88
|
+
|
|
89
|
+
[... more files tested in parallel ...]
|
|
90
|
+
|
|
91
|
+
๐ฏ OVERALL RESULTS
|
|
92
|
+
============================================================
|
|
93
|
+
Total files processed: 15
|
|
94
|
+
Successful: 15
|
|
95
|
+
Failed: 0
|
|
96
|
+
```
|
|
97
|
+
|
|
13
98
|
## Features
|
|
14
99
|
|
|
100
|
+
- **Directory Support**: Test entire directories recursively - finds and tests all `**/*.xml` files
|
|
101
|
+
- **Parallel Execution**: CPU-core-based thread pools for blazing-fast testing of multiple rules and examples
|
|
15
102
|
- **Rule Validation**: Tests PMD rules against their documented examples with detailed test results
|
|
16
103
|
- **XPath Analysis**: Analyzes XPath expressions for node types, operators, attributes, and conditionals
|
|
17
104
|
- **Coverage Checking**: Validates that XPath expressions are properly tested with line number references
|
|
105
|
+
- **LCOV Coverage Reports**: Generate `coverage/lcov.info` files tracking XPath line coverage
|
|
18
106
|
- **Hardcoded Value Detection**: Identifies values that should be parameterized
|
|
19
107
|
- **Line Number Tracking**: Shows exact line numbers in XML files for missing coverage items
|
|
20
108
|
- **TypeScript**: Written in TypeScript for better maintainability
|
|
21
109
|
- **100% Test Coverage**: All code is thoroughly tested (lines, functions, branches, statements)
|
|
22
110
|
- **Modular Architecture**: Clean separation of concerns with low complexity functions
|
|
23
111
|
|
|
112
|
+
## How does it work?
|
|
113
|
+
|
|
114
|
+
The tool extracts examples from `<example>` tags in your PMD rule XML files and validates them against the rule's XPath expression. Examples must be configured to indicate which code should trigger violations and which should be valid.
|
|
115
|
+
|
|
116
|
+
### Example Configuration
|
|
117
|
+
|
|
118
|
+
Examples can be marked using two different formats:
|
|
119
|
+
|
|
120
|
+
#### Format 1: Section Headers
|
|
121
|
+
|
|
122
|
+
Use `// Violation:` and `// Valid:` comments to mark sections of code:
|
|
123
|
+
|
|
124
|
+
```xml
|
|
125
|
+
<example>
|
|
126
|
+
// Violation: Public method should trigger rule
|
|
127
|
+
public class TestClass {
|
|
128
|
+
public void testMethod() {
|
|
129
|
+
// method body
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Valid: Private method should not trigger rule
|
|
134
|
+
public class ValidClass {
|
|
135
|
+
private void testMethod() {
|
|
136
|
+
// method body
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
</example>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Format 2: Inline Markers
|
|
143
|
+
|
|
144
|
+
Use `// โ` for violations and `// โ
` for valid code:
|
|
145
|
+
|
|
146
|
+
```xml
|
|
147
|
+
<example>
|
|
148
|
+
public class TestClass {
|
|
149
|
+
public void violationMethod() { // โ Void method should trigger rule
|
|
150
|
+
// method body
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private Integer validMethod() { // โ
Integer method should not trigger rule
|
|
154
|
+
// method body
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
</example>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### How Examples Are Processed
|
|
161
|
+
|
|
162
|
+
1. **Extraction**: The tool reads all `<example>` tags from your rule XML file
|
|
163
|
+
2. **Parsing**: Each example is parsed to identify violation and valid code sections
|
|
164
|
+
3. **Test File Creation**: Temporary Apex test files are generated with the example code
|
|
165
|
+
4. **PMD Execution**: PMD is run against the test files to check if violations occur as expected
|
|
166
|
+
5. **Validation**: The tool verifies that:
|
|
167
|
+
- Violation examples actually trigger the rule
|
|
168
|
+
- Valid examples do not trigger the rule
|
|
169
|
+
- XPath expressions are properly covered by the examples
|
|
170
|
+
|
|
171
|
+
### Multiple Examples
|
|
172
|
+
|
|
173
|
+
You can include multiple `<example>` tags in a single rule XML file. Each example is tested independently:
|
|
174
|
+
|
|
175
|
+
```xml
|
|
176
|
+
<rule name="MyRule" ...>
|
|
177
|
+
<example>
|
|
178
|
+
// First example with violations and valid code
|
|
179
|
+
...
|
|
180
|
+
</example>
|
|
181
|
+
|
|
182
|
+
<example>
|
|
183
|
+
// Second example with different scenarios
|
|
184
|
+
...
|
|
185
|
+
</example>
|
|
186
|
+
</rule>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Requirements
|
|
190
|
+
|
|
191
|
+
- **Node.js**: โฅ25.0.0
|
|
192
|
+
- **PMD CLI**: Available in PATH (see [PMD Installation](https://pmd.github.io/pmd/pmd_userdocs_installation.html))
|
|
193
|
+
- **Package Manager**: pnpm โฅ10.0.0 (recommended)
|
|
194
|
+
|
|
24
195
|
## Installation
|
|
25
196
|
|
|
26
197
|
```bash
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
cd test-pmd-rule
|
|
30
|
-
|
|
31
|
-
# Install dependencies
|
|
32
|
-
pnpm install
|
|
198
|
+
# Install from npm
|
|
199
|
+
npm install -g test-pmd-rule
|
|
33
200
|
|
|
34
|
-
#
|
|
35
|
-
|
|
201
|
+
# Or use npx to run without installing globally
|
|
202
|
+
npx test-pmd-rule path/to/rule.xml
|
|
36
203
|
```
|
|
37
204
|
|
|
38
205
|
## Usage
|
|
39
206
|
|
|
40
207
|
```bash
|
|
41
208
|
# Test a single rule
|
|
42
|
-
|
|
209
|
+
test-pmd-rule path/to/rule.xml
|
|
43
210
|
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
211
|
+
# Test all XML files in a directory (recursive)
|
|
212
|
+
test-pmd-rule ../sca-extra/rulesets
|
|
213
|
+
|
|
214
|
+
# Generate LCOV coverage reports
|
|
215
|
+
test-pmd-rule rulesets/code-style/AvoidMagicNumbers.xml --coverage
|
|
216
|
+
|
|
217
|
+
# Test directory with coverage reports
|
|
218
|
+
test-pmd-rule ../sca-extra/rulesets --coverage
|
|
219
|
+
|
|
220
|
+
# Or use npx without installing globally
|
|
221
|
+
npx test-pmd-rule path/to/rule.xml
|
|
47
222
|
```
|
|
48
223
|
|
|
224
|
+
**Arguments:**
|
|
225
|
+
|
|
226
|
+
- `<rule.xml|directory>`: Path to XML rule file or directory containing XML files (recursive)
|
|
227
|
+
- `--coverage`: Generate LCOV coverage report in `coverage/lcov.info`
|
|
228
|
+
|
|
49
229
|
The tool will:
|
|
50
230
|
|
|
51
|
-
1.
|
|
52
|
-
2.
|
|
53
|
-
3.
|
|
54
|
-
4.
|
|
55
|
-
5.
|
|
56
|
-
6.
|
|
57
|
-
7.
|
|
231
|
+
1. **Directory Discovery**: If given a directory, recursively find all `**/*.xml` files
|
|
232
|
+
2. **Parallel Processing**: Test multiple files concurrently using CPU-core-based thread pools
|
|
233
|
+
3. **Extract Examples**: Parse examples from `<example>` tags in PMD rule XML files
|
|
234
|
+
4. **Parse Markers**: Identify violation (`// โ` or `// Violation:`) and valid (`// โ
` or `// Valid:`) code sections
|
|
235
|
+
5. **Test File Creation**: Generate temporary Apex test files with example code
|
|
236
|
+
6. **Parallel PMD Execution**: Run PMD against test files with concurrent workers
|
|
237
|
+
7. **Validation**: Verify violations occur for violation examples and don't occur for valid examples
|
|
238
|
+
8. **XPath Coverage Analysis**: Analyze XPath expressions and show coverage with line numbers
|
|
239
|
+
9. **Coverage Reports**: Generate LCOV format reports when `--coverage` flag is used
|
|
240
|
+
10. **Comprehensive Results**: Report detailed test results with parallel processing stats
|
|
58
241
|
|
|
59
|
-
##
|
|
242
|
+
## Coverage Reporting
|
|
60
243
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
244
|
+
When using the `--coverage` flag, the tool generates LCOV format coverage reports in `coverage/lcov.info`. This tracks which lines of your XPath expressions are covered by your test examples.
|
|
245
|
+
|
|
246
|
+
### Coverage Data
|
|
247
|
+
|
|
248
|
+
- **XPath Lines**: Tracks coverage of XPath expression lines in the XML rule files
|
|
249
|
+
- **Component Lines**: Tracks coverage of XPath components (node types, attributes, etc.)
|
|
250
|
+
- **LCOV Format**: Compatible with coverage tools like VS Code Coverage Gutters, GitHub Actions, etc.
|
|
251
|
+
|
|
252
|
+
### Example LCOV Output
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
SF:rulesets/code-style/MyRule.xml
|
|
256
|
+
DA:75,1
|
|
257
|
+
DA:76,1
|
|
258
|
+
DA:79,0
|
|
259
|
+
DA:82,1
|
|
260
|
+
end_of_record
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
This shows that lines 75, 76, 79, and 82 in `MyRule.xml` were executed, with line 79 having 0 coverage (uncovered XPath code).
|
|
64
264
|
|
|
65
265
|
## Development
|
|
66
266
|
|
|
@@ -110,6 +310,9 @@ pnpm format:check
|
|
|
110
310
|
|
|
111
311
|
# Pre-commit checks (format, lint, test coverage)
|
|
112
312
|
pnpm pre-commit
|
|
313
|
+
|
|
314
|
+
# Test with coverage reports
|
|
315
|
+
pnpm test:coverage
|
|
113
316
|
```
|
|
114
317
|
|
|
115
318
|
### Project Structure
|
|
@@ -118,6 +321,9 @@ pnpm pre-commit
|
|
|
118
321
|
src/
|
|
119
322
|
โโโ cli/ # Command-line interface
|
|
120
323
|
โ โโโ main.ts # CLI entry point
|
|
324
|
+
โโโ coverage/ # Coverage reporting
|
|
325
|
+
โ โโโ generateLcov.ts # LCOV report generation
|
|
326
|
+
โ โโโ trackCoverage.ts # Coverage data collection
|
|
121
327
|
โโโ pmd/ # PMD execution utilities
|
|
122
328
|
โ โโโ runPMD.ts # PMD CLI execution
|
|
123
329
|
โ โโโ parseViolations.ts # XML violation parsing
|
|
@@ -125,6 +331,8 @@ src/
|
|
|
125
331
|
โ โโโ parseExample.ts # Example code parsing
|
|
126
332
|
โ โโโ extractMarkers.ts # Violation/valid marker extraction
|
|
127
333
|
โ โโโ createTestFile.ts # Test file generation
|
|
334
|
+
โโโ utils/ # Utility functions
|
|
335
|
+
โ โโโ concurrency.ts # Parallel execution utilities
|
|
128
336
|
โโโ xpath/ # XPath analysis and validation
|
|
129
337
|
โ โโโ extractXPath.ts # XPath extraction from XML
|
|
130
338
|
โ โโโ analyzeXPath.ts # XPath analysis orchestration
|
|
@@ -151,49 +359,6 @@ tests/ # Unit and integration tests
|
|
|
151
359
|
docs/ # Documentation (symlinked from agent-docs)
|
|
152
360
|
```
|
|
153
361
|
|
|
154
|
-
## Output Format
|
|
155
|
-
|
|
156
|
-
The tool provides detailed output including:
|
|
157
|
-
|
|
158
|
-
- **Test Details**: Individual test results for each example (violation/valid tests)
|
|
159
|
-
- **Test Summary**: Overall statistics (examples tested, passed, total violations)
|
|
160
|
-
- **XPath Coverage**: Detailed coverage analysis showing:
|
|
161
|
-
- Node types coverage with line numbers for missing items
|
|
162
|
-
- Conditionals coverage (only missing items shown)
|
|
163
|
-
- Attributes coverage with line numbers for missing items
|
|
164
|
-
- Operators coverage
|
|
165
|
-
- **Final Status**: Overall pass/fail status
|
|
166
|
-
|
|
167
|
-
Example output:
|
|
168
|
-
|
|
169
|
-
```
|
|
170
|
-
๐งช Testing rule: rulesets/code-style/MyRule.xml
|
|
171
|
-
|
|
172
|
-
๐ Test Details:
|
|
173
|
-
- Example 1 Test: Violation โ
|
|
174
|
-
- Example 1 Test: Valid โ
|
|
175
|
-
- Example 2 Test: Violation โ
|
|
176
|
-
|
|
177
|
-
๐ Test Summary:
|
|
178
|
-
Examples tested: 2
|
|
179
|
-
Examples passed: 2
|
|
180
|
-
Total violations: 3
|
|
181
|
-
Rule triggers violations: โ
Yes
|
|
182
|
-
|
|
183
|
-
๐ XPath Coverage:
|
|
184
|
-
Status: โ ๏ธ Incomplete
|
|
185
|
-
Coverage items: 2
|
|
186
|
-
1. โ ๏ธ Node types: 4/6 covered
|
|
187
|
-
Missing:
|
|
188
|
-
- Line 43: VariableDeclaration
|
|
189
|
-
- Line 50: VariableExpression
|
|
190
|
-
2. โ ๏ธ Attributes: 2/3 covered
|
|
191
|
-
Missing:
|
|
192
|
-
- Line 52: BeginLine
|
|
193
|
-
|
|
194
|
-
โ
All tests passed!
|
|
195
|
-
```
|
|
196
|
-
|
|
197
362
|
## Documentation
|
|
198
363
|
|
|
199
364
|
Comprehensive documentation is available in the [`docs/`](docs/) directory (symlinked from [agent-docs](https://github.com/starch-uk/agent-docs/)):
|
package/dist/test-pmd-rule.js
CHANGED
|
@@ -976,6 +976,43 @@ function createTestFile({
|
|
|
976
976
|
} else {
|
|
977
977
|
codeToInclude = [...parsed.violations, ...parsed.valids];
|
|
978
978
|
}
|
|
979
|
+
const hasClassDefinition = codeToInclude.some(
|
|
980
|
+
(line) => line.trim().startsWith("public class ") || line.trim().startsWith("class ")
|
|
981
|
+
);
|
|
982
|
+
if (hasClassDefinition) {
|
|
983
|
+
const extractedCode = [];
|
|
984
|
+
let insideMethod = false;
|
|
985
|
+
let braceDepth = 0;
|
|
986
|
+
for (const line of codeToInclude) {
|
|
987
|
+
const trimmed = line.trim();
|
|
988
|
+
if (trimmed.startsWith("public class ") || trimmed.startsWith("class ") || trimmed.startsWith("private class ")) {
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
const isMethodSignature = (trimmed.startsWith("public ") || trimmed.startsWith("private ") || trimmed.startsWith("protected ")) && trimmed.includes("() {") && !insideMethod;
|
|
992
|
+
const INITIAL_BRACE_DEPTH = 1;
|
|
993
|
+
const ZERO_BRACE_DEPTH = 0;
|
|
994
|
+
if (isMethodSignature) {
|
|
995
|
+
insideMethod = true;
|
|
996
|
+
braceDepth = INITIAL_BRACE_DEPTH;
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
if (insideMethod) {
|
|
1000
|
+
const openBraces = (line.match(/{/g) ?? []).length;
|
|
1001
|
+
const closeBraces = (line.match(/}/g) ?? []).length;
|
|
1002
|
+
braceDepth += openBraces - closeBraces;
|
|
1003
|
+
if (braceDepth > ZERO_BRACE_DEPTH) {
|
|
1004
|
+
extractedCode.push(line);
|
|
1005
|
+
}
|
|
1006
|
+
if (braceDepth <= ZERO_BRACE_DEPTH) {
|
|
1007
|
+
insideMethod = false;
|
|
1008
|
+
braceDepth = ZERO_BRACE_DEPTH;
|
|
1009
|
+
}
|
|
1010
|
+
} else if (!trimmed.startsWith("}") && trimmed.length > EMPTY_LENGTH && !trimmed.startsWith("public ") && !trimmed.startsWith("private ")) {
|
|
1011
|
+
extractedCode.push(line);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
codeToInclude = extractedCode.length > EMPTY_LENGTH ? extractedCode : codeToInclude;
|
|
1015
|
+
}
|
|
979
1016
|
if (codeToInclude.length > EMPTY_LENGTH) {
|
|
980
1017
|
classContent += ` public void testMethod${String(exampleIndex)}() {
|
|
981
1018
|
`;
|
|
@@ -2083,7 +2120,8 @@ async function main() {
|
|
|
2083
2120
|
console.error("\u274C Invalid path argument");
|
|
2084
2121
|
process.exit(EXIT_CODE_ERROR);
|
|
2085
2122
|
}
|
|
2086
|
-
const
|
|
2123
|
+
const COVERAGE_FLAG = "--coverage";
|
|
2124
|
+
const hasCoverageFlag = args.length > SECOND_ARG_INDEX && args[SECOND_ARG_INDEX] === COVERAGE_FLAG;
|
|
2087
2125
|
if (!existsSync2(pathArg)) {
|
|
2088
2126
|
console.error(`\u274C Path not found: ${pathArg}`);
|
|
2089
2127
|
process.exit(EXIT_CODE_ERROR);
|
|
@@ -2158,9 +2196,13 @@ async function main() {
|
|
|
2158
2196
|
).map(
|
|
2159
2197
|
(tracker) => tracker.getCoverageData()
|
|
2160
2198
|
);
|
|
2199
|
+
const COVERAGE_REPORT_PATH = "coverage/lcov.info";
|
|
2161
2200
|
try {
|
|
2162
|
-
generateLcovReport(coverageData,
|
|
2163
|
-
console.log(
|
|
2201
|
+
generateLcovReport(coverageData, COVERAGE_REPORT_PATH);
|
|
2202
|
+
console.log(
|
|
2203
|
+
`
|
|
2204
|
+
\u{1F4CA} Coverage report generated: ${COVERAGE_REPORT_PATH}`
|
|
2205
|
+
);
|
|
2164
2206
|
} catch (error) {
|
|
2165
2207
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2166
2208
|
console.error(
|