test-pmd-rule 1.2.2 → 1.2.4
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 +7 -0
- package/dist/test-pmd-rule.js +63 -16
- package/dist/test-pmd-rule.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,6 +162,10 @@ public class TestClass {
|
|
|
162
162
|
1. **Extraction**: The tool reads all `<example>` tags from your rule XML file
|
|
163
163
|
2. **Parsing**: Each example is parsed to identify violation and valid code sections
|
|
164
164
|
3. **Test File Creation**: Temporary Apex test files are generated with the example code
|
|
165
|
+
- If an example contains a single top-level class, it's renamed to a test class name
|
|
166
|
+
- If an example contains multiple top-level classes, they're wrapped as inner classes within a test class
|
|
167
|
+
- If an example contains only methods/fields without a class, they're wrapped in a test class
|
|
168
|
+
- If an example contains only standalone code, it's wrapped in a test method within a test class
|
|
165
169
|
4. **PMD Execution**: PMD is run against the test files to check if violations occur as expected
|
|
166
170
|
5. **Validation**: The tool verifies that:
|
|
167
171
|
- Violation examples actually trigger the rule
|
|
@@ -213,6 +217,7 @@ The AST nodes are color-coded to indicate which parts of your example code are b
|
|
|
213
217
|
- **Dark Red (dim)**: Node is tested by **violation** examples but was already covered in previous examples
|
|
214
218
|
- **Green (bright)**: Node is tested by **valid** examples in the current example
|
|
215
219
|
- **Dark Green (dim)**: Node is tested by **valid** examples but was already covered in previous examples
|
|
220
|
+
- **Orange**: Node matches both **violation** and **valid** sections (indicates ambiguity - the same code appears in both sections)
|
|
216
221
|
- **No color**: Node is not tested or couldn't be matched to example code
|
|
217
222
|
|
|
218
223
|
This color coding helps you understand:
|
|
@@ -220,6 +225,7 @@ This color coding helps you understand:
|
|
|
220
225
|
- Which AST nodes correspond to your violation/valid markers
|
|
221
226
|
- Whether nodes are being tested by the current example or were already covered
|
|
222
227
|
- Which parts of the AST structure your XPath expression should target
|
|
228
|
+
- When the same code appears in both violation and valid sections (orange indicates this ambiguity)
|
|
223
229
|
|
|
224
230
|
**Notes:**
|
|
225
231
|
|
|
@@ -228,6 +234,7 @@ This color coding helps you understand:
|
|
|
228
234
|
- If the generated test file has syntax errors, the tool will display the generated file content to help debug issues
|
|
229
235
|
- The AST output includes all node attributes and can be quite verbose - use it when you need detailed insight into PMD's parsing
|
|
230
236
|
- Colors use ANSI escape codes and will automatically be disabled if your terminal doesn't support them
|
|
237
|
+
- **Wrapper class cleanup**: The tool automatically strips wrapper class prefixes from `DefiningType` attributes (e.g., `DefiningType='TestClass1.MyClassViolation'` becomes `DefiningType='MyClassViolation'`) to make the output cleaner and more readable
|
|
231
238
|
|
|
232
239
|
## Requirements
|
|
233
240
|
|
package/dist/test-pmd-rule.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/main.ts
|
|
4
|
-
import { existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
4
|
+
import { existsSync as existsSync2, readdirSync, realpathSync as realpathSync2, statSync } from "fs";
|
|
5
5
|
import { extname, resolve as resolve3 } from "path";
|
|
6
6
|
import { argv } from "process";
|
|
7
7
|
import { cpus } from "os";
|
|
8
|
-
import {
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
9
|
import { DOMParser as DOMParser4 } from "@xmldom/xmldom";
|
|
10
10
|
import { stringifyTree } from "stringify-tree";
|
|
11
11
|
|
|
@@ -1392,6 +1392,7 @@ var SPACE_CHAR = " ";
|
|
|
1392
1392
|
var DEFAULT_INDENT = " ";
|
|
1393
1393
|
var FIRST_ELEMENT_INDEX2 = 0;
|
|
1394
1394
|
var LAST_ELEMENT_OFFSET = 1;
|
|
1395
|
+
var SINGLE_COUNT = 1;
|
|
1395
1396
|
function removeInlineMarkers(line) {
|
|
1396
1397
|
if (line.includes("// \u274C")) {
|
|
1397
1398
|
const splitResult = line.split("// \u274C");
|
|
@@ -1611,16 +1612,15 @@ function createTestFile({
|
|
|
1611
1612
|
const TEST_CLASS_NAME = `TestClass${String(exampleIndex)}`;
|
|
1612
1613
|
const fullExampleContent = parsed.content;
|
|
1613
1614
|
const ZERO_BRACE_DEPTH = 0;
|
|
1614
|
-
const
|
|
1615
|
-
let
|
|
1615
|
+
const topLevelClassCount = (() => {
|
|
1616
|
+
let count = 0;
|
|
1616
1617
|
let braceDepth = ZERO_BRACE_DEPTH;
|
|
1617
1618
|
const exampleLines = fullExampleContent.split("\n");
|
|
1618
1619
|
for (const line of exampleLines) {
|
|
1619
1620
|
const trimmed = line.trim();
|
|
1620
1621
|
const isClassDef = trimmed.startsWith("public class ") || trimmed.startsWith("class ") || trimmed.startsWith("private class ");
|
|
1621
1622
|
if (isClassDef && braceDepth === ZERO_BRACE_DEPTH) {
|
|
1622
|
-
|
|
1623
|
-
break;
|
|
1623
|
+
count++;
|
|
1624
1624
|
}
|
|
1625
1625
|
const openBracesMatch = line.match(/{/g);
|
|
1626
1626
|
const closeBracesMatch = line.match(/}/g);
|
|
@@ -1628,12 +1628,20 @@ function createTestFile({
|
|
|
1628
1628
|
const closeBraces = closeBracesMatch ? closeBracesMatch.length : ZERO_BRACE_DEPTH;
|
|
1629
1629
|
braceDepth += openBraces - closeBraces;
|
|
1630
1630
|
}
|
|
1631
|
-
return
|
|
1631
|
+
return count;
|
|
1632
1632
|
})();
|
|
1633
|
+
const classDefCountInIncluded = codeToInclude.filter((line) => {
|
|
1634
|
+
const trimmed = line.trim();
|
|
1635
|
+
return trimmed.startsWith("public class ") || trimmed.startsWith("class ") || trimmed.startsWith("private class ");
|
|
1636
|
+
}).length;
|
|
1637
|
+
const hasTopLevelClass = topLevelClassCount === SINGLE_COUNT || topLevelClassCount > SINGLE_COUNT && classDefCountInIncluded <= SINGLE_COUNT;
|
|
1633
1638
|
const hasClassLikeStructures = (() => {
|
|
1634
1639
|
if (hasTopLevelClass) {
|
|
1635
1640
|
return false;
|
|
1636
1641
|
}
|
|
1642
|
+
if (topLevelClassCount > SINGLE_COUNT) {
|
|
1643
|
+
return classDefCountInIncluded > SINGLE_COUNT;
|
|
1644
|
+
}
|
|
1637
1645
|
const fieldDeclarationRegex = /^\s*(public|private|protected)\s+\w+\s+\w+\s*[;=]/;
|
|
1638
1646
|
return codeToInclude.some((line) => {
|
|
1639
1647
|
const trimmed = line.trim();
|
|
@@ -3619,12 +3627,20 @@ var NODE_TYPE_ELEMENT = 1;
|
|
|
3619
3627
|
var EMPTY_ARRAY_LENGTH = 0;
|
|
3620
3628
|
var SINGLE_ELEMENT_INDEX = 0;
|
|
3621
3629
|
var MIN_ARRAY_INDEX = 0;
|
|
3630
|
+
var DOT_SEPARATOR_LENGTH = 1;
|
|
3622
3631
|
function isCliInvocation() {
|
|
3623
3632
|
const [, entryPath] = argv;
|
|
3624
3633
|
if (entryPath === void 0) {
|
|
3625
3634
|
return false;
|
|
3626
3635
|
}
|
|
3627
|
-
|
|
3636
|
+
try {
|
|
3637
|
+
const currentModulePath = fileURLToPath(import.meta.url);
|
|
3638
|
+
const resolvedEntryPath = resolve3(process.cwd(), entryPath);
|
|
3639
|
+
const absoluteEntryPath = realpathSync2(resolvedEntryPath);
|
|
3640
|
+
return currentModulePath === absoluteEntryPath;
|
|
3641
|
+
} catch {
|
|
3642
|
+
return false;
|
|
3643
|
+
}
|
|
3628
3644
|
}
|
|
3629
3645
|
function findXmlFiles(directory) {
|
|
3630
3646
|
const xmlFiles = [];
|
|
@@ -3760,8 +3776,16 @@ function removeWrappersFromXmlDom(doc, exampleIndex, wrapperInfo) {
|
|
|
3760
3776
|
const allNodes2 = doc.getElementsByTagName("*");
|
|
3761
3777
|
for (const node of Array.from(allNodes2)) {
|
|
3762
3778
|
const definingType = node.getAttribute("DefiningType");
|
|
3779
|
+
if (definingType === null) {
|
|
3780
|
+
continue;
|
|
3781
|
+
}
|
|
3763
3782
|
if (definingType === wrapperClassName2) {
|
|
3764
3783
|
node.removeAttribute("DefiningType");
|
|
3784
|
+
} else if (definingType.startsWith(`${wrapperClassName2}.`)) {
|
|
3785
|
+
const classNameWithoutPrefix = definingType.slice(
|
|
3786
|
+
wrapperClassName2.length + DOT_SEPARATOR_LENGTH
|
|
3787
|
+
);
|
|
3788
|
+
node.setAttribute("DefiningType", classNameWithoutPrefix);
|
|
3765
3789
|
}
|
|
3766
3790
|
}
|
|
3767
3791
|
return;
|
|
@@ -3921,8 +3945,16 @@ function removeWrappersFromXmlDom(doc, exampleIndex, wrapperInfo) {
|
|
|
3921
3945
|
const allNodes = doc.getElementsByTagName("*");
|
|
3922
3946
|
for (const node of Array.from(allNodes)) {
|
|
3923
3947
|
const definingType = node.getAttribute("DefiningType");
|
|
3948
|
+
if (definingType === null) {
|
|
3949
|
+
continue;
|
|
3950
|
+
}
|
|
3924
3951
|
if (definingType === wrapperClassName) {
|
|
3925
3952
|
node.removeAttribute("DefiningType");
|
|
3953
|
+
} else if (definingType.startsWith(`${wrapperClassName}.`)) {
|
|
3954
|
+
const classNameWithoutPrefix = definingType.slice(
|
|
3955
|
+
wrapperClassName.length + DOT_SEPARATOR_LENGTH
|
|
3956
|
+
);
|
|
3957
|
+
node.setAttribute("DefiningType", classNameWithoutPrefix);
|
|
3926
3958
|
}
|
|
3927
3959
|
}
|
|
3928
3960
|
}
|
|
@@ -4052,16 +4084,18 @@ function determineNodeColor(options) {
|
|
|
4052
4084
|
return void 0;
|
|
4053
4085
|
}
|
|
4054
4086
|
}
|
|
4055
|
-
let
|
|
4056
|
-
let
|
|
4087
|
+
let violationMatchCount = 0;
|
|
4088
|
+
let validMatchCount = 0;
|
|
4057
4089
|
for (const lineNum of matchingLines) {
|
|
4058
4090
|
const section = lineSectionMap.get(lineNum);
|
|
4059
4091
|
if (section === "violation") {
|
|
4060
|
-
|
|
4092
|
+
violationMatchCount++;
|
|
4061
4093
|
} else if (section === "valid") {
|
|
4062
|
-
|
|
4094
|
+
validMatchCount++;
|
|
4063
4095
|
}
|
|
4064
4096
|
}
|
|
4097
|
+
const hasViolationSection = violationMatchCount > EMPTY_MARKERS_LENGTH;
|
|
4098
|
+
const hasValidSection = validMatchCount > EMPTY_MARKERS_LENGTH;
|
|
4065
4099
|
let alreadyCovered = false;
|
|
4066
4100
|
if (xpath !== null && previousExamplesCoverage !== void 0 && previousExamplesCoverage.length > EMPTY_COVERAGE_LENGTH) {
|
|
4067
4101
|
const nodeTypeInXPath = xpath.includes(nodeType);
|
|
@@ -4140,10 +4174,19 @@ function determineNodeColor(options) {
|
|
|
4140
4174
|
}
|
|
4141
4175
|
}
|
|
4142
4176
|
}
|
|
4143
|
-
if (hasViolationSection &&
|
|
4177
|
+
if (hasViolationSection && hasValidSection) {
|
|
4178
|
+
if (violationMarkers.length > EMPTY_MARKERS_LENGTH && validMarkers.length > EMPTY_MARKERS_LENGTH) {
|
|
4179
|
+
return "orange";
|
|
4180
|
+
}
|
|
4181
|
+
if (violationMarkers.length > EMPTY_MARKERS_LENGTH) {
|
|
4182
|
+
return alreadyCovered ? "dark-red" : "red";
|
|
4183
|
+
}
|
|
4184
|
+
if (validMarkers.length > EMPTY_MARKERS_LENGTH) {
|
|
4185
|
+
return alreadyCovered ? "dark-green" : "green";
|
|
4186
|
+
}
|
|
4187
|
+
} else if (hasViolationSection && violationMarkers.length > EMPTY_MARKERS_LENGTH) {
|
|
4144
4188
|
return alreadyCovered ? "dark-red" : "red";
|
|
4145
|
-
}
|
|
4146
|
-
if (hasValidSection && validMarkers.length > EMPTY_MARKERS_LENGTH) {
|
|
4189
|
+
} else if (hasValidSection && validMarkers.length > EMPTY_MARKERS_LENGTH) {
|
|
4147
4190
|
return alreadyCovered ? "dark-green" : "green";
|
|
4148
4191
|
}
|
|
4149
4192
|
return void 0;
|
|
@@ -4243,6 +4286,7 @@ function applyColorToNodeName(nodeName, color) {
|
|
|
4243
4286
|
const ANSI_DARK_GREEN = "\x1B[32;2m";
|
|
4244
4287
|
const ANSI_RED = "\x1B[31m";
|
|
4245
4288
|
const ANSI_DARK_RED = "\x1B[31;2m";
|
|
4289
|
+
const ANSI_ORANGE = "\x1B[33m";
|
|
4246
4290
|
if (color === "green") {
|
|
4247
4291
|
return `${ANSI_GREEN}${nodeName}${ANSI_RESET}`;
|
|
4248
4292
|
}
|
|
@@ -4252,7 +4296,10 @@ function applyColorToNodeName(nodeName, color) {
|
|
|
4252
4296
|
if (color === "red") {
|
|
4253
4297
|
return `${ANSI_RED}${nodeName}${ANSI_RESET}`;
|
|
4254
4298
|
}
|
|
4255
|
-
|
|
4299
|
+
if (color === "dark-red") {
|
|
4300
|
+
return `${ANSI_DARK_RED}${nodeName}${ANSI_RESET}`;
|
|
4301
|
+
}
|
|
4302
|
+
return `${ANSI_ORANGE}${nodeName}${ANSI_RESET}`;
|
|
4256
4303
|
}
|
|
4257
4304
|
function parseXmlAstAndStripWrappers(options) {
|
|
4258
4305
|
const {
|