-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NEW (RegexEngine) @W-15985046@ Implement regex engine with trailing w…
…hitespace rule (#34)
- Loading branch information
1 parent
39d669d
commit 1f269c1
Showing
11 changed files
with
318 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { Violation } from "@salesforce/code-analyzer-engine-api"; | ||
import fs from "node:fs"; | ||
import path from "node:path"; | ||
import os from "node:os" | ||
|
||
const APEX_CLASS_FILE_EXT: string = ".cls" | ||
|
||
export class RegexExecutor { | ||
lineSep: string | ||
|
||
constructor() { | ||
this.lineSep = os.EOL | ||
} | ||
|
||
async execute(allFiles: string[]): Promise<Violation[]> { | ||
let violations: Violation[] = [] | ||
|
||
for (const file of allFiles) { | ||
const fileData = fs.statSync(file) | ||
if (fileData.isFile()) { | ||
violations = violations.concat(await this.scanFile(file)); | ||
} | ||
} | ||
return violations; | ||
} | ||
|
||
private getColumnNumber(fileContents: string, charIndex: number, newlineIndexes: number[]): number { | ||
/*TODO: swap out findIndex for a modified binary search implementation */ | ||
const idxOfNextNewline = newlineIndexes.findIndex(el => el >= charIndex) | ||
const idxOfCurrentLine = idxOfNextNewline === -1 ? newlineIndexes.length - 1: idxOfNextNewline - 1 | ||
const eolOffset = this.lineSep.length - 1 | ||
return charIndex - newlineIndexes.at(idxOfCurrentLine)! - eolOffset | ||
} | ||
|
||
private getLineNumber(fileContents: string, charIndex: number, newlineIndexes: number[]): number{ | ||
const idxOfNextNewline = newlineIndexes.findIndex(el => el >= charIndex) | ||
return idxOfNextNewline === -1 ? newlineIndexes.length + 1 : idxOfNextNewline + 1; | ||
} | ||
|
||
private async scanFile(fileName: string): Promise<Violation[]> { | ||
const violations: Violation[] = []; | ||
if (path.extname((fileName)) === APEX_CLASS_FILE_EXT) { | ||
const fileContents: string = fs.readFileSync(fileName, {encoding: 'utf8'}) | ||
const regex: RegExp = /[ \t]+((?=\r?\n)|(?=$))/g; | ||
const matches = fileContents.matchAll(regex); | ||
const newlineIndexes = this.getNewlineIndices(fileContents); | ||
|
||
for (const match of matches) { | ||
const codeLocation = { | ||
file: fileName, | ||
startLine: this.getLineNumber(fileContents, match.index, newlineIndexes), | ||
startColumn: this.getColumnNumber(fileContents, match.index, newlineIndexes), | ||
endLine: this.getLineNumber(fileContents,match.index + match[0].length, newlineIndexes), | ||
endColumn: this.getColumnNumber(fileContents,match.index + match[0].length, newlineIndexes) | ||
} | ||
violations.push({ | ||
ruleName: "TrailingWhitespaceRule", | ||
codeLocations: [codeLocation], | ||
primaryLocationIndex: 0, | ||
message: "Detects trailing whitespace (tabs or spaces) at the end of lines of code and lines that are only whitespace." | ||
}); | ||
} | ||
} | ||
return violations; | ||
} | ||
|
||
private getNewlineIndices(fileContents: string): number[] { | ||
const newlineRegex: RegExp = new RegExp(this.lineSep, "g") | ||
const matches = fileContents.matchAll(newlineRegex); | ||
const newlineIndexes = [] | ||
|
||
for (const match of matches) { | ||
newlineIndexes.push(match.index); | ||
} | ||
return newlineIndexes | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
|
||
import { changeWorkingDirectoryToPackageRoot } from "./test-helpers"; | ||
import path from "node:path"; | ||
import { RegexExecutor } from '../src/executor'; | ||
import { | ||
EXPECTED_VIOLATION_1, | ||
EXPECTED_VIOLATION_2, | ||
EXPECTED_VIOLATION_3 | ||
} from './test-config'; | ||
import { Violation } from "@salesforce/code-analyzer-engine-api"; | ||
|
||
changeWorkingDirectoryToPackageRoot(); | ||
|
||
describe("Executor tests", () => { | ||
let executor: RegexExecutor; | ||
beforeAll(() => { | ||
executor = new RegexExecutor(); | ||
}); | ||
|
||
/* I realize that I am not totally certain what the intended behavior of this case should be. But it might be better as just a no-op*/ | ||
it("If I have a file that's not an Apex class, execute() should not return any violations.", async () => | ||
{ | ||
const file = path.resolve("test", "test-data", "1_notApexClassWithWhitespace", "something.xml") | ||
const violations: Violation[] = await executor.execute([file]) | ||
const expViolations: Violation[] = []; | ||
expect(violations).toStrictEqual(expViolations); | ||
}); | ||
|
||
it("If execute() is called with an Apex class that has trailing whitespace, emit violation", async () => { | ||
const file = path.resolve("test", "test-data", "2_apexClasses", "myOuterClass.cls") | ||
const violations: Violation[] = await executor.execute([file]) | ||
|
||
expect(violations).toStrictEqual(EXPECTED_VIOLATION_1) | ||
}); | ||
|
||
it("If execute() is pointed to an Apex class without trailing whitespace ensure there are no erroneous violations", async () => { | ||
const file = path.resolve("test", "test-data", "4_ApexClassWithoutWhitespace", "myOuterClass.cls") | ||
const violations: Violation[] = await executor.execute([file]) | ||
const expViolations: Violation[] = []; | ||
expect(violations).toStrictEqual(expViolations); | ||
|
||
}); | ||
|
||
it('Ensure that execute() can catch multiple violations in the same file', async () => { | ||
const file = path.resolve("test", "test-data", "2_apexClasses", "myClass.cls"); | ||
const violations: Violation[] = await executor.execute([file]); | ||
expect(violations).toStrictEqual(EXPECTED_VIOLATION_2) | ||
}) | ||
|
||
it("Ensure execute() can be called on a list Apex classes and properly emits errors", async () => { | ||
const file1 = path.resolve("test", "test-data", "2_apexClasses", "myOuterClass.cls") | ||
const file2 = path.resolve("test", "test-data", "2_apexClasses", "myClass.cls"); | ||
const file3 = path.resolve("test", "test-data", "4_ApexClassWithoutWhitespace", "myOuterClass.cls") | ||
const violations: Violation[] = await executor.execute([file1, file2, file3]) | ||
expect(violations).toStrictEqual(EXPECTED_VIOLATION_3) | ||
}); | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import path from "node:path"; | ||
import { changeWorkingDirectoryToPackageRoot } from "./test-helpers"; | ||
import { CodeLocation, Violation} from "@salesforce/code-analyzer-engine-api"; | ||
|
||
changeWorkingDirectoryToPackageRoot(); | ||
|
||
export const FILE_LOCATION_1 = path.resolve(__dirname, "test-data", "2_apexClasses", "myOuterClass.cls") | ||
export const FILE_LOCATION_2 = path.resolve(__dirname, "test-data", "2_apexClasses", "myClass.cls") | ||
export const TRAILING_WHITESPACE_RULE_NAME = "TrailingWhitespaceRule" | ||
export const TRAILING_WHITESPACE_RULE_MESSAGE = "Detects trailing whitespace (tabs or spaces) at the end of lines of code and lines that are only whitespace."; | ||
export const TRAILING_WHITESPACE_RESOURCE_URLS = [] | ||
|
||
const EXPECTED_CODE_LOCATION_1: CodeLocation = { | ||
file: FILE_LOCATION_1, | ||
startLine: 6, | ||
startColumn: 2, | ||
endLine: 6, | ||
endColumn: 4 | ||
} | ||
|
||
const EXPECTED_CODE_LOCATION_2: CodeLocation = { | ||
file: FILE_LOCATION_2, | ||
startLine: 2, | ||
startColumn: 40, | ||
endLine: 2, | ||
endColumn: 41 | ||
} | ||
|
||
const EXPECTED_CODE_LOCATION_3: CodeLocation = { | ||
file: FILE_LOCATION_2, | ||
startLine: 6, | ||
startColumn: 2, | ||
endLine: 6, | ||
endColumn: 4 | ||
} | ||
|
||
export const EXPECTED_VIOLATION_1: Violation[] = [ | ||
{ | ||
ruleName: TRAILING_WHITESPACE_RULE_NAME, | ||
message: TRAILING_WHITESPACE_RULE_MESSAGE, | ||
primaryLocationIndex: 0, | ||
codeLocations: [EXPECTED_CODE_LOCATION_1] | ||
} | ||
] | ||
|
||
export const EXPECTED_VIOLATION_2: Violation[] = [ | ||
{ | ||
ruleName: TRAILING_WHITESPACE_RULE_NAME, | ||
message: TRAILING_WHITESPACE_RULE_MESSAGE, | ||
primaryLocationIndex: 0, | ||
codeLocations: [EXPECTED_CODE_LOCATION_2] | ||
}, | ||
{ | ||
ruleName: TRAILING_WHITESPACE_RULE_NAME, | ||
message: TRAILING_WHITESPACE_RULE_MESSAGE, | ||
primaryLocationIndex: 0, | ||
codeLocations: [EXPECTED_CODE_LOCATION_3] | ||
|
||
} | ||
] | ||
|
||
export const EXPECTED_VIOLATION_3: Violation[] = [ | ||
{ | ||
ruleName: TRAILING_WHITESPACE_RULE_NAME, | ||
message: TRAILING_WHITESPACE_RULE_MESSAGE, | ||
primaryLocationIndex: 0, | ||
codeLocations: [EXPECTED_CODE_LOCATION_1] | ||
}, | ||
{ | ||
ruleName: TRAILING_WHITESPACE_RULE_NAME, | ||
message: TRAILING_WHITESPACE_RULE_MESSAGE, | ||
primaryLocationIndex: 0, | ||
codeLocations: [EXPECTED_CODE_LOCATION_2] | ||
}, | ||
{ | ||
ruleName: TRAILING_WHITESPACE_RULE_NAME, | ||
message: TRAILING_WHITESPACE_RULE_MESSAGE, | ||
primaryLocationIndex: 0, | ||
codeLocations: [EXPECTED_CODE_LOCATION_3] | ||
|
||
} | ||
] | ||
|
14 changes: 14 additions & 0 deletions
14
...ages/code-analyzer-regex-engine/test/test-data/1_notApexClassWithWhitespace/something.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<root> | ||
<describe horn="out"> | ||
<!--model before raise lot outer-->main | ||
</describe> | ||
<history include="bush"> | ||
<cut game="slowly">excitement</cut> | ||
<suppose very="someone">wood</suppose> | ||
<four hand="stop"> | ||
<!--fine-->-807916209.3514104 | ||
</four> | ||
</history> | ||
<!--truth stems plastic--> | ||
<chose>-927308825</chose> | ||
</root> |
6 changes: 6 additions & 0 deletions
6
packages/code-analyzer-regex-engine/test/test-data/2_apexClasses/myClass.cls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
public class myCls { | ||
// Additional myOuterClass code here | ||
class myInnerClass { | ||
// myInnerClass code here | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/code-analyzer-regex-engine/test/test-data/2_apexClasses/myOuterClass.cls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
public class myOuterClass { | ||
// Additional myOuterClass code here | ||
class myInnerClass { | ||
// myInnerClass code here | ||
} | ||
} |
Oops, something went wrong.