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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # test-pmd-rule
2
2
 
3
- A standalone PMD Rule Tester for Apex rules, with XPath analysis and coverage validation.
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
  [![Node Version](https://img.shields.io/badge/node-%3E%3D25.0.0-brightgreen)](https://nodejs.org/)
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue)](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
- # Clone the repository
28
- git clone https://github.com/your-org/test-pmd-rule.git
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
- # Build the project
35
- pnpm build
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
- node dist/test-pmd-rule.js path/to/rule.xml
209
+ test-pmd-rule path/to/rule.xml
43
210
 
44
- # Or after building
45
- pnpm build
46
- node dist/test-pmd-rule.js rulesets/code-style/AvoidMagicNumbers.xml
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. Extract examples from the PMD rule XML file
52
- 2. Parse violation and valid markers from example code
53
- 3. Create temporary Apex test files
54
- 4. Run PMD against the test files
55
- 5. Validate that violations occur for violation examples and don't occur for valid examples
56
- 6. Analyze XPath coverage and show missing items with line numbers
57
- 7. Report comprehensive test results
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
- ## Requirements
242
+ ## Coverage Reporting
60
243
 
61
- - **Node.js**: โ‰ฅ25.0.0
62
- - **PMD CLI**: Available in PATH (see [PMD Installation](https://pmd.github.io/pmd/pmd_userdocs_installation.html))
63
- - **Package Manager**: pnpm โ‰ฅ10.0.0 (recommended)
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/)):
@@ -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 hasCoverageFlag = args.length > SECOND_ARG_INDEX && args[SECOND_ARG_INDEX] === "--coverage";
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, "coverage/lcov.info");
2163
- console.log("\n\u{1F4CA} Coverage report generated: coverage/lcov.info");
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(