From e5c95345b44b03ce288e2bc35477a2d451b3eeba Mon Sep 17 00:00:00 2001 From: Arun Tyagi Date: Thu, 19 Feb 2026 13:33:24 +0530 Subject: [PATCH 1/5] Updated Node dependencies for code-analyzer-core package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Changes ### code-analyzer-core (0.43.0-SNAPSHOT) Updated packages with ^ symbol: - semver: ^7.7.3 → ^7.7.4 - rimraf: ^6.1.2 → ^6.1.3 - typescript-eslint: ^8.53.0 → ^8.56.0 **Constraints followed:** - ✅ Kept @types/node at ^20.0.0 (Node 20 is minimum customer version) - ✅ Kept eslint at ^9.39.2 (v10 has breaking changes) - ✅ Kept @eslint/js at ^9.39.2 (v10 has breaking changes) - ✅ No version bump (already using 0.43.0-SNAPSHOT suffix) ## Testing - ✅ Build successful - ✅ Tests: 282 passed - ✅ No breaking changes introduced ## Context This update was done after merging all engine dependency updates: - PMD engine upgrade (7.20.0 → 7.21.0) - ESLint engines upgrade - All other engines (engine-api, flow, regex, retirejs, sfge) All engine references remain compatible with their updated versions. --- package-lock.json | 6 +++--- packages/code-analyzer-core/package.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 25f3e4fb..308d80e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9449,7 +9449,7 @@ "@types/node": "^20.0.0", "csv-stringify": "^6.6.0", "js-yaml": "^4.1.1", - "semver": "^7.7.3", + "semver": "^7.7.4", "xmlbuilder": "^15.1.1" }, "devDependencies": { @@ -9461,10 +9461,10 @@ "cross-env": "^10.1.0", "eslint": "^9.39.2", "jest": "^30.2.0", - "rimraf": "^6.1.2", + "rimraf": "^6.1.3", "ts-jest": "^29.4.6", "typescript": "^5.9.3", - "typescript-eslint": "^8.53.0" + "typescript-eslint": "^8.56.0" }, "engines": { "node": ">=20.0.0" diff --git a/packages/code-analyzer-core/package.json b/packages/code-analyzer-core/package.json index 3299e29e..311bc24c 100644 --- a/packages/code-analyzer-core/package.json +++ b/packages/code-analyzer-core/package.json @@ -20,7 +20,7 @@ "@types/node": "^20.0.0", "csv-stringify": "^6.6.0", "js-yaml": "^4.1.1", - "semver": "^7.7.3", + "semver": "^7.7.4", "xmlbuilder": "^15.1.1" }, "devDependencies": { @@ -32,10 +32,10 @@ "cross-env": "^10.1.0", "eslint": "^9.39.2", "jest": "^30.2.0", - "rimraf": "^6.1.2", + "rimraf": "^6.1.3", "ts-jest": "^29.4.6", "typescript": "^5.9.3", - "typescript-eslint": "^8.53.0" + "typescript-eslint": "^8.56.0" }, "engines": { "node": ">=20.0.0" From 9601b19f863e52472063d16664274b0bab733a33 Mon Sep 17 00:00:00 2001 From: Arun Tyagi Date: Thu, 19 Feb 2026 14:24:59 +0530 Subject: [PATCH 2/5] Fix NoTrailingWhitespace rule to exclude lines with only whitespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Issue The NoTrailingWhitespace regex rule was flagging empty lines that contain only whitespace (indentation). This created false positives when developers use empty lines with indentation to separate code sections. Example of false positive: ```apex public void execute(SchedulableContext ctx) { Messaging.reserveSingleEmailCapacity(1); Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage(); List results = Messaging.sendEmail(...); } ``` The empty lines with indentation (lines 3, 5, 7) were being flagged as violations, even though they improve readability and are not counted towards code character limits. ## Solution Updated the regex pattern from: ```typescript /(?[ \t]+)((\r?\n)|$)/g ``` To: ```typescript /(?<=\S.*)(?[ \t]+)((\r?\n)|$)/g ``` The new pattern uses a positive lookbehind `(?<=\S.*)` to ensure there's at least one non-whitespace character earlier on the line before matching trailing whitespace. ## Behavior After Fix - ✅ Still flags: `code content here ` (trailing whitespace after content) - ❌ No longer flags: ` ` (line with only whitespace/indentation) ## Testing - ✅ All 65 existing tests pass - ✅ Existing test cases still correctly flag legitimate trailing whitespace violations after actual code content --- packages/code-analyzer-regex-engine/src/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/code-analyzer-regex-engine/src/plugin.ts b/packages/code-analyzer-regex-engine/src/plugin.ts index 5c5943cf..3c2a4c5d 100644 --- a/packages/code-analyzer-regex-engine/src/plugin.ts +++ b/packages/code-analyzer-regex-engine/src/plugin.ts @@ -72,7 +72,7 @@ function validateEngineName(engineName: string) { export function createBaseRegexRules(now: Date): RegexRules { return { NoTrailingWhitespace: { - regex: (/(?[ \t]+)((\r?\n)|$)/g).toString(), + regex: (/(?<=\S.*)(?[ \t]+)((\r?\n)|$)/g).toString(), file_extensions: ['.cls', '.trigger'], // Currently restricted to apex files only... but we might want to extend where this rule applies in the future description: getMessage('TrailingWhitespaceRuleDescription'), violation_message: getMessage('TrailingWhitespaceRuleMessage'), From 97c9d950731c6de38097384d784af1ba9c941033 Mon Sep 17 00:00:00 2001 From: Arun Tyagi Date: Thu, 19 Feb 2026 14:58:35 +0530 Subject: [PATCH 3/5] Enhance NoTrailingWhitespace rule to detect multiple consecutive empty lines Updated regex to detect both trailing whitespace after code and multiple consecutive empty lines, while allowing single empty lines between code sections. The rule now comprehensively handles all whitespace violations without false positives on lines containing only whitespace. --- packages/code-analyzer-regex-engine/src/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/code-analyzer-regex-engine/src/plugin.ts b/packages/code-analyzer-regex-engine/src/plugin.ts index 3c2a4c5d..6328d3f6 100644 --- a/packages/code-analyzer-regex-engine/src/plugin.ts +++ b/packages/code-analyzer-regex-engine/src/plugin.ts @@ -72,7 +72,7 @@ function validateEngineName(engineName: string) { export function createBaseRegexRules(now: Date): RegexRules { return { NoTrailingWhitespace: { - regex: (/(?<=\S.*)(?[ \t]+)((\r?\n)|$)/g).toString(), + regex: (/((?<=\S.*)(?[ \t]+)(?=\r?\n|$))|((?<=\r?\n\r?\n)(?\r?\n))/g).toString(), file_extensions: ['.cls', '.trigger'], // Currently restricted to apex files only... but we might want to extend where this rule applies in the future description: getMessage('TrailingWhitespaceRuleDescription'), violation_message: getMessage('TrailingWhitespaceRuleMessage'), From a848e74c26cfc9e72f82baf3e1d9edeac01bd0e2 Mon Sep 17 00:00:00 2001 From: Arun Tyagi Date: Thu, 19 Feb 2026 15:20:47 +0530 Subject: [PATCH 4/5] Add tests for NoTrailingWhitespace rule multiple empty lines detection - Added test data files with multiple consecutive empty lines - Added test data file with empty lines at EOF - Added test data file with single empty lines (valid case) - Added test cases to verify multiple empty lines are flagged - Added test case to verify single empty lines are allowed - Updated existing test to include new violations from test data --- .../test/engine.test.ts | 70 +++++++++++++++++++ .../4_multipleEmptyLines/EmptyLinesAtEof.cls | 7 ++ .../MultipleEmptyLines.cls | 10 +++ .../SingleEmptyLines.cls | 13 ++++ 4 files changed, 100 insertions(+) create mode 100644 packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/EmptyLinesAtEof.cls create mode 100644 packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/MultipleEmptyLines.cls create mode 100644 packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/5_singleEmptyLinesValid/SingleEmptyLines.cls diff --git a/packages/code-analyzer-regex-engine/test/engine.test.ts b/packages/code-analyzer-regex-engine/test/engine.test.ts index b72026d4..ac730081 100644 --- a/packages/code-analyzer-regex-engine/test/engine.test.ts +++ b/packages/code-analyzer-regex-engine/test/engine.test.ts @@ -268,7 +268,69 @@ describe('Tests for runRules', () => { endColumn: 21 }] }, + { + ruleName: "NoTrailingWhitespace", + message: getMessage('TrailingWhitespaceRuleMessage'), + primaryLocationIndex: 0, + codeLocations: [{ + file: path.resolve(__dirname, "test-data", "apexClassWhitespace", "4_multipleEmptyLines", "EmptyLinesAtEof.cls"), + startLine: 7, + startColumn: 1, + endLine: 8, + endColumn: 1 + }] + }, + { + ruleName: "NoTrailingWhitespace", + message: getMessage('TrailingWhitespaceRuleMessage'), + primaryLocationIndex: 0, + codeLocations: [{ + file: path.resolve(__dirname, "test-data", "apexClassWhitespace", "4_multipleEmptyLines", "MultipleEmptyLines.cls"), + startLine: 6, + startColumn: 1, + endLine: 7, + endColumn: 1 + }] + }, + + ]; + + expect(runResults.violations).toHaveLength(expectedViolations.length); + for (const expectedViolation of expectedViolations) { + expect(runResults.violations).toContainEqual(expectedViolation); + } + }); + it("NoTrailingWhitespace rule should flag multiple consecutive empty lines", async () => { + const runOptions: RunOptions = createRunOptions( + new Workspace('id', [path.resolve(__dirname, "test-data", "apexClassWhitespace", "4_multipleEmptyLines")])); + const runResults: EngineRunResults = await engine.runRules(["NoTrailingWhitespace"], runOptions); + + const expectedViolations: Violation[] = [ + { + ruleName: "NoTrailingWhitespace", + message: getMessage('TrailingWhitespaceRuleMessage'), + primaryLocationIndex: 0, + codeLocations: [{ + file: path.resolve(__dirname, "test-data", "apexClassWhitespace", "4_multipleEmptyLines", "MultipleEmptyLines.cls"), + startLine: 6, + startColumn: 1, + endLine: 7, + endColumn: 1 + }] + }, + { + ruleName: "NoTrailingWhitespace", + message: getMessage('TrailingWhitespaceRuleMessage'), + primaryLocationIndex: 0, + codeLocations: [{ + file: path.resolve(__dirname, "test-data", "apexClassWhitespace", "4_multipleEmptyLines", "EmptyLinesAtEof.cls"), + startLine: 7, + startColumn: 1, + endLine: 8, + endColumn: 1 + }] + } ]; expect(runResults.violations).toHaveLength(expectedViolations.length); @@ -277,6 +339,14 @@ describe('Tests for runRules', () => { } }); + it("NoTrailingWhitespace rule should NOT flag single empty lines between code", async () => { + const runOptions: RunOptions = createRunOptions( + new Workspace('id', [path.resolve(__dirname, "test-data", "apexClassWhitespace", "5_singleEmptyLinesValid")])); + const runResults: EngineRunResults = await engine.runRules(["NoTrailingWhitespace"], runOptions); + + expect(runResults.violations).toHaveLength(0); + }); + it("Ensure runRules when called on a directory of Apex classes with getHeapSize in a loop, it properly emits violations", async () => { const runOptions: RunOptions = createRunOptions( new Workspace('id', [path.resolve(__dirname, "test-data", "apexClassGetLimitsInLoop")])); diff --git a/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/EmptyLinesAtEof.cls b/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/EmptyLinesAtEof.cls new file mode 100644 index 00000000..145a08fd --- /dev/null +++ b/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/EmptyLinesAtEof.cls @@ -0,0 +1,7 @@ +public class EmptyLinesAtEof { + public void method1() { + System.debug('test'); + } +} + + diff --git a/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/MultipleEmptyLines.cls b/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/MultipleEmptyLines.cls new file mode 100644 index 00000000..1efa8698 --- /dev/null +++ b/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/MultipleEmptyLines.cls @@ -0,0 +1,10 @@ +public class MultipleEmptyLines { + public void method1() { + System.debug('test1'); + } + + + public void method2() { + System.debug('test2'); + } +} diff --git a/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/5_singleEmptyLinesValid/SingleEmptyLines.cls b/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/5_singleEmptyLinesValid/SingleEmptyLines.cls new file mode 100644 index 00000000..3cdf7ff1 --- /dev/null +++ b/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/5_singleEmptyLinesValid/SingleEmptyLines.cls @@ -0,0 +1,13 @@ +public class SingleEmptyLines { + public void method1() { + System.debug('test1'); + } + + public void method2() { + System.debug('test2'); + } + + public void method3() { + System.debug('test3'); + } +} From e85963effb83919725243ef08b83c1d2bc73a31d Mon Sep 17 00:00:00 2001 From: Arun Tyagi Date: Thu, 19 Feb 2026 17:44:05 +0530 Subject: [PATCH 5/5] Fix regex to detect empty lines containing only whitespace Addressed review feedback: The previous regex only detected consecutive newlines that were immediately adjacent (\n\n\n). It failed to detect multiple consecutive empty lines when those lines contained only spaces or tabs (\n \n \n). Updated regex to treat [ \t]*\r?\n (optional whitespace + newline) as an empty line, rather than just \r?\n. This ensures that lines containing only whitespace are properly treated as empty when checking for multiple consecutive empty lines. Changes: - Updated regex pattern: (?<=([ \t]*\r?\n)([ \t]*\r?\n))[ \t]*\r?\n - Added test file EmptyLinesWithWhitespace.cls with spaces on empty lines - Added specific test case for whitespace-only empty lines - Updated existing tests to include new violation --- .../code-analyzer-regex-engine/src/plugin.ts | 2 +- .../test/engine.test.ts | 50 +++++++++++++++++++ .../EmptyLinesWithWhitespace.cls | 10 ++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/EmptyLinesWithWhitespace.cls diff --git a/packages/code-analyzer-regex-engine/src/plugin.ts b/packages/code-analyzer-regex-engine/src/plugin.ts index 6e387ccb..e44c3fef 100644 --- a/packages/code-analyzer-regex-engine/src/plugin.ts +++ b/packages/code-analyzer-regex-engine/src/plugin.ts @@ -72,7 +72,7 @@ function validateEngineName(engineName: string) { export function createBaseRegexRules(now: Date): RegexRules { return { NoTrailingWhitespace: { - regex: (/(?((?<=\S.*)[ \t]+(?=\r?\n|$))|((?<=\r?\n\r?\n)\r?\n))/g).toString(), + regex: (/(?((?<=\S.*)[ \t]+(?=\r?\n|$))|((?<=([ \t]*\r?\n)([ \t]*\r?\n))[ \t]*\r?\n))/g).toString(), file_extensions: ['.cls', '.trigger'], // Currently restricted to apex files only... but we might want to extend where this rule applies in the future description: getMessage('TrailingWhitespaceRuleDescription'), violation_message: getMessage('TrailingWhitespaceRuleMessage'), diff --git a/packages/code-analyzer-regex-engine/test/engine.test.ts b/packages/code-analyzer-regex-engine/test/engine.test.ts index ac730081..b852423d 100644 --- a/packages/code-analyzer-regex-engine/test/engine.test.ts +++ b/packages/code-analyzer-regex-engine/test/engine.test.ts @@ -292,6 +292,18 @@ describe('Tests for runRules', () => { endColumn: 1 }] }, + { + ruleName: "NoTrailingWhitespace", + message: getMessage('TrailingWhitespaceRuleMessage'), + primaryLocationIndex: 0, + codeLocations: [{ + file: path.resolve(__dirname, "test-data", "apexClassWhitespace", "4_multipleEmptyLines", "EmptyLinesWithWhitespace.cls"), + startLine: 6, + startColumn: 1, + endLine: 7, + endColumn: 1 + }] + }, ]; @@ -330,6 +342,18 @@ describe('Tests for runRules', () => { endLine: 8, endColumn: 1 }] + }, + { + ruleName: "NoTrailingWhitespace", + message: getMessage('TrailingWhitespaceRuleMessage'), + primaryLocationIndex: 0, + codeLocations: [{ + file: path.resolve(__dirname, "test-data", "apexClassWhitespace", "4_multipleEmptyLines", "EmptyLinesWithWhitespace.cls"), + startLine: 6, + startColumn: 1, + endLine: 7, + endColumn: 1 + }] } ]; @@ -347,6 +371,32 @@ describe('Tests for runRules', () => { expect(runResults.violations).toHaveLength(0); }); + it("NoTrailingWhitespace rule should flag multiple empty lines that contain only whitespace", async () => { + const runOptions: RunOptions = createRunOptions( + new Workspace('id', [path.resolve(__dirname, "test-data", "apexClassWhitespace", "4_multipleEmptyLines", "EmptyLinesWithWhitespace.cls")])); + const runResults: EngineRunResults = await engine.runRules(["NoTrailingWhitespace"], runOptions); + + const expectedViolations: Violation[] = [ + { + ruleName: "NoTrailingWhitespace", + message: getMessage('TrailingWhitespaceRuleMessage'), + primaryLocationIndex: 0, + codeLocations: [{ + file: path.resolve(__dirname, "test-data", "apexClassWhitespace", "4_multipleEmptyLines", "EmptyLinesWithWhitespace.cls"), + startLine: 6, + startColumn: 1, + endLine: 7, + endColumn: 1 + }] + } + ]; + + expect(runResults.violations).toHaveLength(expectedViolations.length); + for (const expectedViolation of expectedViolations) { + expect(runResults.violations).toContainEqual(expectedViolation); + } + }); + it("Ensure runRules when called on a directory of Apex classes with getHeapSize in a loop, it properly emits violations", async () => { const runOptions: RunOptions = createRunOptions( new Workspace('id', [path.resolve(__dirname, "test-data", "apexClassGetLimitsInLoop")])); diff --git a/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/EmptyLinesWithWhitespace.cls b/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/EmptyLinesWithWhitespace.cls new file mode 100644 index 00000000..5c62bd54 --- /dev/null +++ b/packages/code-analyzer-regex-engine/test/test-data/apexClassWhitespace/4_multipleEmptyLines/EmptyLinesWithWhitespace.cls @@ -0,0 +1,10 @@ +public class EmptyLinesWithWhitespace { + public void method1() { + System.debug('test1'); + } + + + public void method2() { + System.debug('test2'); + } +}