diff --git a/.node-scripts/validate-changed-package-versions.js b/.node-scripts/validate-changed-package-versions.js index 6c22398f..5239e716 100644 --- a/.node-scripts/validate-changed-package-versions.js +++ b/.node-scripts/validate-changed-package-versions.js @@ -77,13 +77,16 @@ function identifyIncorrectlyVersionedPackages(changedPackages) { const incorrectlyVersionedPackages = []; for (const changedPackage of changedPackages) { + if (isPackageThatHasNotPublished(changedPackage)) { + continue; // We can't check previous versions of this package because it hasn't published yet, so the version must be correct + } const packageJsonPath = path.join(changedPackage, 'package.json'); if (!fs.existsSync(packageJsonPath)) { continue; // This means the package was deleted, so we ignore this package } + const packageVersion = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')).version; - if (!packageVersion.endsWith('-SNAPSHOT')) { incorrectlyVersionedPackages.push(`${changedPackage} (currently versioned as ${packageVersion}) lacks a trailing "-SNAPSHOT"`); continue; @@ -109,4 +112,10 @@ function getLatestReleasedVersion(changedPackage) { } } +function isPackageThatHasNotPublished(changedPackage) { + return [ + "packages/ENGINE-TEMPLATE" + ].includes(changedPackage.replace("\\","/")); +} + main(); diff --git a/package-lock.json b/package-lock.json index ce95839c..efce85db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8783,14 +8783,15 @@ }, "packages/code-analyzer-core": { "name": "@salesforce/code-analyzer-core", - "version": "0.34.0", + "version": "0.35.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "csv-stringify": "^6.6.0", "js-yaml": "^4.1.0", "semver": "^7.7.2", + "tmp": "^0.2.3", "xmlbuilder": "^15.1.1" }, "devDependencies": { @@ -8799,6 +8800,7 @@ "@types/js-yaml": "^4.0.9", "@types/sarif": "^2.1.7", "@types/semver": "^7.7.0", + "@types/tmp": "^0.2.6", "cross-env": "^10.0.0", "eslint": "^9.32.0", "jest": "^30.0.5", @@ -9043,16 +9045,14 @@ }, "packages/code-analyzer-engine-api": { "name": "@salesforce/code-analyzer-engine-api", - "version": "0.28.0", + "version": "0.29.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@types/node": "^20.0.0", - "tmp": "^0.2.3" + "@types/node": "^20.0.0" }, "devDependencies": { "@eslint/js": "^9.32.0", "@types/jest": "^30.0.0", - "@types/tmp": "^0.2.6", "eslint": "^9.32.0", "jest": "^30.0.5", "rimraf": "^6.0.1", @@ -9303,7 +9303,7 @@ "@lwc/eslint-plugin-lwc": "^3.2.0", "@lwc/eslint-plugin-lwc-platform": "^6.1.0", "@salesforce-ux/eslint-plugin-slds": "^0.5.0", - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@salesforce/code-analyzer-eslint8-engine": "0.6.0-SNAPSHOT", "@salesforce/eslint-config-lwc": "^4.0.0", "@salesforce/eslint-plugin-lightning": "^2.0.0", @@ -9321,7 +9321,6 @@ "devDependencies": { "@types/jest": "^30.0.0", "@types/semver": "^7.7.0", - "@types/tmp": "^0.2.6", "@types/unzipper": "^0.10.11", "cross-env": "^10.0.0", "jest": "^30.0.5", @@ -9851,7 +9850,7 @@ "@eslint/js": "8.57.1", "@lwc/eslint-plugin-lwc": "2.2.0", "@lwc/eslint-plugin-lwc-platform": "5.2.0", - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@salesforce/eslint-config-lwc": "3.7.2", "@salesforce/eslint-plugin-lightning": "1.0.1", "@types/node": "^20.0.0", @@ -10246,7 +10245,7 @@ "version": "0.26.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -10500,7 +10499,7 @@ "version": "0.30.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -10754,7 +10753,7 @@ "version": "0.27.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.4", "p-limit": "^3.1.0" @@ -11008,7 +11007,7 @@ "version": "0.26.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.4", "node-stream-zip": "^1.15.0", @@ -11263,7 +11262,7 @@ "version": "0.12.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -11517,7 +11516,7 @@ "version": "0.1.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0" }, "devDependencies": { diff --git a/packages/ENGINE-TEMPLATE/package.json b/packages/ENGINE-TEMPLATE/package.json index 26011c8c..ee3effe6 100644 --- a/packages/ENGINE-TEMPLATE/package.json +++ b/packages/ENGINE-TEMPLATE/package.json @@ -14,7 +14,7 @@ "types": "dist/index.d.ts", "dependencies": { "@types/node": "^20.0.0", - "@salesforce/code-analyzer-engine-api": "0.28.0" + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT" }, "devDependencies": { "@eslint/js": "^9.32.0", @@ -36,7 +36,7 @@ ], "scripts": { "build": "tsc --build tsconfig.build.json --verbose", - "test": "jest --coverage", + "test": "tsc --build tsconfig.json && jest --coverage", "lint": "eslint src/**/*.ts", "package": "npm pack", "all": "npm run build && npm run lint && npm run test && npm run package", diff --git a/packages/ENGINE-TEMPLATE/test/engine.test.ts b/packages/ENGINE-TEMPLATE/test/engine.test.ts index 2fbc6be2..35197c28 100644 --- a/packages/ENGINE-TEMPLATE/test/engine.test.ts +++ b/packages/ENGINE-TEMPLATE/test/engine.test.ts @@ -1,9 +1,8 @@ import { EngineRunResults, RuleDescription, RunOptions, Workspace } from "@salesforce/code-analyzer-engine-api"; -import fs from "node:fs"; -import * as os from "node:os"; -import path from "path"; +import * as fs from "node:fs"; +import * as path from "path"; import { TemplateEngine } from "../src/engine"; -import { changeWorkingDirectoryToPackageRoot } from "./test-helpers"; +import { changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions } from "./test-helpers"; changeWorkingDirectoryToPackageRoot(); @@ -41,7 +40,7 @@ describe('Template Engine Tests', () => { // add more checks for specific rules, describe options, and logging events it('When no workspace is provided, then all rules are returned', async () => { const engine: TemplateEngine = new TemplateEngine(); - const rules: RuleDescription[] = await engine.describeRules({logFolder: os.tmpdir()}); + const rules: RuleDescription[] = await engine.describeRules(createDescribeOptions()); expect(rules).toEqual(ALL_EXPECTED_RULES); }); @@ -64,11 +63,4 @@ describe('Template Engine Tests', () => { const expectedRulesJsonStr: string = (await fs.promises.readFile(path.join(TEST_DATA_FOLDER, relativeExpectedFile), 'utf-8')); return JSON.parse(expectedRulesJsonStr) as RuleDescription[]; } - - function createRunOptions(workspace: Workspace): RunOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } - } -}); +}); \ No newline at end of file diff --git a/packages/ENGINE-TEMPLATE/test/test-helpers.ts b/packages/ENGINE-TEMPLATE/test/test-helpers.ts index 4d98690e..6b9bd681 100644 --- a/packages/ENGINE-TEMPLATE/test/test-helpers.ts +++ b/packages/ENGINE-TEMPLATE/test/test-helpers.ts @@ -1,5 +1,8 @@ -import process from "node:process"; -import path from "node:path"; +import { DescribeOptions, RunOptions, Workspace } from "@salesforce/code-analyzer-engine-api"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as process from "node:process"; export function changeWorkingDirectoryToPackageRoot() { let original_working_directory: string; @@ -16,3 +19,19 @@ export function changeWorkingDirectoryToPackageRoot() { process.chdir(original_working_directory); }); } + +export function createDescribeOptions(workspace?: Workspace): DescribeOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} + +export function createRunOptions(workspace: Workspace): RunOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} diff --git a/packages/ENGINE-TEMPLATE/tsconfig.json b/packages/ENGINE-TEMPLATE/tsconfig.json index 8f5b9142..111c54ac 100644 --- a/packages/ENGINE-TEMPLATE/tsconfig.json +++ b/packages/ENGINE-TEMPLATE/tsconfig.json @@ -1,10 +1,8 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "composite": true, - "outDir": "./dist", "rootDir": ".", - "isolatedModules": true + "noEmit": true }, "include": [ "./src", diff --git a/packages/code-analyzer-core/package.json b/packages/code-analyzer-core/package.json index 2525691f..aae4f5d8 100644 --- a/packages/code-analyzer-core/package.json +++ b/packages/code-analyzer-core/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-core", "description": "Core Package for the Salesforce Code Analyzer", - "version": "0.34.0", + "version": "0.35.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -16,12 +16,13 @@ }, "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "csv-stringify": "^6.6.0", "js-yaml": "^4.1.0", "semver": "^7.7.2", - "xmlbuilder": "^15.1.1" + "xmlbuilder": "^15.1.1", + "tmp": "^0.2.3" }, "devDependencies": { "@eslint/js": "^9.32.0", @@ -29,6 +30,7 @@ "@types/jest": "^30.0.0", "@types/sarif": "^2.1.7", "@types/semver": "^7.7.0", + "@types/tmp": "^0.2.6", "cross-env": "^10.0.0", "eslint": "^9.32.0", "jest": "^30.0.5", @@ -48,7 +50,7 @@ ], "scripts": { "build": "tsc --build tsconfig.build.json --verbose", - "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage", + "test": "tsc --build tsconfig.json && cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage", "lint": "eslint src/**/*.ts", "package": "npm pack", "all": "npm run build && npm run lint && npm run test && npm run package", diff --git a/packages/code-analyzer-core/src/utils.ts b/packages/code-analyzer-core/src/utils.ts index bc6cd2cd..71c905dd 100644 --- a/packages/code-analyzer-core/src/utils.ts +++ b/packages/code-analyzer-core/src/utils.ts @@ -1,11 +1,16 @@ -import path from "node:path"; +import * as tmp from 'tmp'; +import * as path from "node:path"; import crypto from "node:crypto"; -import fs from "node:fs"; -import {createTempDir} from "@salesforce/code-analyzer-engine-api/utils"; +import * as fs from "node:fs"; +import {promisify} from "node:util"; // THIS FILE CONTAINS UTILITIES WHICH ARE USED INTERNALLY ONLY. // None of the following exported interfaces and functions should be exported from the index file. +tmp.setGracefulCleanup(); +const tmpDirAsync = promisify((options: tmp.DirOptions, cb: tmp.DirCallback) => tmp.dir(options, cb)); + + export function toAbsolutePath(fileOrFolder: string): string { // Convert slashes to platform specific slashes and then convert to absolute path return path.resolve(fileOrFolder.replace(/[\\/]/g, path.sep)); @@ -39,7 +44,7 @@ export class RuntimeTempFolder implements TempFolder { async getPath(): Promise { if (!this.rootFolder) { - this.rootFolder = await createTempDir(); + this.rootFolder = await tmpDirAsync({keep: false, unsafeCleanup: true}); } return this.rootFolder; } @@ -125,4 +130,4 @@ export function deepEquals(value1: unknown, value2: unknown): boolean { // For all other types (number, string, boolean, etc.), use strict equality return false; -} +} \ No newline at end of file diff --git a/packages/code-analyzer-core/tsconfig.json b/packages/code-analyzer-core/tsconfig.json index 00559307..46994aff 100644 --- a/packages/code-analyzer-core/tsconfig.json +++ b/packages/code-analyzer-core/tsconfig.json @@ -1,10 +1,8 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "composite": true, - "outDir": "./dist", "rootDir": ".", - "isolatedModules": true + "noEmit": true }, "include": [ "./src", diff --git a/packages/code-analyzer-engine-api/package.json b/packages/code-analyzer-engine-api/package.json index d5d6ceb2..b1f6672e 100644 --- a/packages/code-analyzer-engine-api/package.json +++ b/packages/code-analyzer-engine-api/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-engine-api", "description": "Engine API Package for the Salesforce Code Analyzer", - "version": "0.28.0", + "version": "0.29.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -16,13 +16,11 @@ }, "types": "dist/index.d.ts", "dependencies": { - "@types/node": "^20.0.0", - "tmp": "^0.2.3" + "@types/node": "^20.0.0" }, "devDependencies": { "@eslint/js": "^9.32.0", "@types/jest": "^30.0.0", - "@types/tmp": "^0.2.6", "eslint": "^9.32.0", "jest": "^30.0.5", "rimraf": "^6.0.1", @@ -40,7 +38,7 @@ ], "scripts": { "build": "tsc --build tsconfig.build.json --verbose", - "test": "jest --coverage", + "test": "tsc --build tsconfig.json && jest --coverage", "lint": "eslint src/**/*.ts", "package": "npm pack", "all": "npm run build && npm run lint && npm run test && npm run package", diff --git a/packages/code-analyzer-engine-api/src/utils/fs-utils.ts b/packages/code-analyzer-engine-api/src/utils/fs-utils.ts index 9f7ce20a..7b1a0618 100644 --- a/packages/code-analyzer-engine-api/src/utils/fs-utils.ts +++ b/packages/code-analyzer-engine-api/src/utils/fs-utils.ts @@ -1,20 +1,6 @@ -import * as tmp from 'tmp'; -import {promisify} from "node:util"; import path from "node:path"; import fs from "node:fs"; -tmp.setGracefulCleanup(); -const tmpDirAsync = promisify((options: tmp.DirOptions, cb: tmp.DirCallback) => tmp.dir(options, cb)); - -/** - * Creates a temporary directory that eventually cleans up after itself - * @param parentTempDir - if supplied, then a temporary folder is placed directly underneath this parent folder. - */ -export async function createTempDir(parentTempDir?: string) : Promise { - return tmpDirAsync({dir: parentTempDir, keep: false, unsafeCleanup: true}); -} - - /** * Returns the longest common parent folder of the provided paths. * If empty or if no common parent folder exists, like in the case on Windows machines of using two different drives diff --git a/packages/code-analyzer-engine-api/src/utils/index.ts b/packages/code-analyzer-engine-api/src/utils/index.ts index f761c135..f19bd2b4 100644 --- a/packages/code-analyzer-engine-api/src/utils/index.ts +++ b/packages/code-analyzer-engine-api/src/utils/index.ts @@ -5,7 +5,6 @@ export { } from './datetime-utils'; export { - createTempDir, calculateLongestCommonParentFolderOf } from './fs-utils'; diff --git a/packages/code-analyzer-engine-api/test/api-v1.test.ts b/packages/code-analyzer-engine-api/test/api-v1.test.ts index 3fd19469..bd55bfac 100644 --- a/packages/code-analyzer-engine-api/test/api-v1.test.ts +++ b/packages/code-analyzer-engine-api/test/api-v1.test.ts @@ -16,6 +16,8 @@ import { Workspace } from "../src"; import * as os from "node:os"; +import * as fs from "node:fs"; +import * as path from "node:path"; describe('Tests for v1', () => { it('EnginePluginV1 getApiVersion should return 1.0', () => { @@ -55,8 +57,8 @@ describe('Tests for v1', () => { }); const workspace: Workspace = new Workspace('id', []); - await dummyEngine.describeRules({workspace: workspace, logFolder: os.tmpdir()}); - await dummyEngine.runRules(["dummy"], {workspace: workspace, logFolder: os.tmpdir()}); + await dummyEngine.describeRules(createDescribeOptions(workspace)); + await dummyEngine.runRules(["dummy"], createRunOptions(workspace)); expect(logEvents).toHaveLength(2); expect(logEvents[0]).toEqual({ @@ -157,3 +159,19 @@ class DummyEngineV1 extends Engine { }; } } + +export function createDescribeOptions(workspace?: Workspace): DescribeOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} + +export function createRunOptions(workspace: Workspace): RunOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} \ No newline at end of file diff --git a/packages/code-analyzer-engine-api/test/utils/utils.test.ts b/packages/code-analyzer-engine-api/test/utils/utils.test.ts index 077c775b..dbb0e0f2 100644 --- a/packages/code-analyzer-engine-api/test/utils/utils.test.ts +++ b/packages/code-analyzer-engine-api/test/utils/utils.test.ts @@ -1,6 +1,4 @@ -import fs from 'node:fs'; -import os from 'node:os'; -import {createTempDir, FixedClock, indent, JavaCommandExecutor, RealClock} from "../../src/utils"; +import {FixedClock, indent, JavaCommandExecutor, RealClock} from "../../src/utils"; describe('Tests for Clock', () => { @@ -26,21 +24,6 @@ describe('Tests for Clock', () => { }); }) -describe('Tests for createTempDir', () => { - it('Successfully creates temporary directory', async () => { - // Before testing, figure out how many entries are in the temp folder. - const preTestTempContentsCount: number = (await fs.promises.readdir(os.tmpdir())).length; - - // Create the directory. - const tempDir: string = await createTempDir(); - - // Verify that the temp folder has at least one additional entry, and that an entry with the temporary name now exists. - const postTestTempContentsCount: number = (await fs.promises.readdir(os.tmpdir())).length; - expect(postTestTempContentsCount).toBeGreaterThan(preTestTempContentsCount); - expect(fs.existsSync(tempDir)).toEqual(true); - }); -}) - describe('Tests for JavaCommandExecutor', () => { it('When a java command fails due to invalid command, then a helpful error should be thrown', async () => { const javaCommandExecutor: JavaCommandExecutor = new JavaCommandExecutor(); diff --git a/packages/code-analyzer-engine-api/tsconfig.json b/packages/code-analyzer-engine-api/tsconfig.json index 00559307..46994aff 100644 --- a/packages/code-analyzer-engine-api/tsconfig.json +++ b/packages/code-analyzer-engine-api/tsconfig.json @@ -1,10 +1,8 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "composite": true, - "outDir": "./dist", "rootDir": ".", - "isolatedModules": true + "noEmit": true }, "include": [ "./src", diff --git a/packages/code-analyzer-eslint-engine/package.json b/packages/code-analyzer-eslint-engine/package.json index 0d8c2219..467a5d8d 100644 --- a/packages/code-analyzer-eslint-engine/package.json +++ b/packages/code-analyzer-eslint-engine/package.json @@ -17,7 +17,7 @@ "@lwc/eslint-plugin-lwc": "^3.2.0", "@lwc/eslint-plugin-lwc-platform": "^6.1.0", "@salesforce-ux/eslint-plugin-slds": "^0.5.0", - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@salesforce/code-analyzer-eslint8-engine": "0.6.0-SNAPSHOT", "@salesforce/eslint-config-lwc": "^4.0.0", "@salesforce/eslint-plugin-lightning": "^2.0.0", @@ -35,7 +35,6 @@ "devDependencies": { "@types/jest": "^30.0.0", "@types/semver": "^7.7.0", - "@types/tmp": "^0.2.6", "@types/unzipper": "^0.10.11", "cross-env": "^10.0.0", "jest": "^30.0.5", @@ -53,7 +52,7 @@ ], "scripts": { "build": "tsc --build tsconfig.build.json --verbose", - "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage", + "test": "tsc --build tsconfig.json && cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage", "lint": "eslint src/**/*.ts", "package": "npm pack", "all": "npm run build && npm run lint && npm run test && npm run package", diff --git a/packages/code-analyzer-eslint-engine/src/engine.ts b/packages/code-analyzer-eslint-engine/src/engine.ts index d73c7e94..afaa13a8 100644 --- a/packages/code-analyzer-eslint-engine/src/engine.ts +++ b/packages/code-analyzer-eslint-engine/src/engine.ts @@ -138,7 +138,7 @@ export class ESLintEngine extends Engine { eslintContext: context, progressRange: [30, 95] // 30% to 95% } - const lintResults: ESLint.LintResult[] = await this._runESLintWorkerTask.run(runTaskInput); + const lintResults: ESLint.LintResult[] = await this._runESLintWorkerTask.run(runTaskInput, runOptions.workingFolder); const engineResults: EngineRunResults = { violations: this.toViolations(lintResults, new Set(ruleNames)) diff --git a/packages/code-analyzer-eslint-engine/src/worker-task.ts b/packages/code-analyzer-eslint-engine/src/worker-task.ts index 7bceb134..c5cbcf4b 100644 --- a/packages/code-analyzer-eslint-engine/src/worker-task.ts +++ b/packages/code-analyzer-eslint-engine/src/worker-task.ts @@ -1,6 +1,6 @@ import * as path from "node:path"; import * as fsp from "node:fs/promises"; -import {createTempDir} from "@salesforce/code-analyzer-engine-api/utils"; +import * as os from "node:os"; import {Serializable, Worker} from "node:worker_threads"; import {EngineEventEmitter, Event} from "@salesforce/code-analyzer-engine-api"; @@ -13,7 +13,7 @@ import {EngineEventEmitter, Event} from "@salesforce/code-analyzer-engine-api"; export abstract class WorkerTask extends EngineEventEmitter { private readonly taskJsFilePath: string; private readonly taskClassName: string; - private workerScriptFile?: string; + private workerScriptFileCache: Map = new Map(); /** * Internal use and testing use only: @@ -47,12 +47,12 @@ export abstract class WorkerTask { + async run(taskInput?: Input, workingFolder?: string): Promise { if (this._runInCurrentThreadInsteadofNewThread) { return this.exec(taskInput); } - const worker: Worker = new Worker(await this.getWorkerScriptFile(), { workerData: { input: taskInput } }); + const worker: Worker = new Worker(await this.getWorkerScriptFile(workingFolder), { workerData: { input: taskInput } }); return new Promise((resolve, reject) => { worker.on('message', (msg: Event | {type: "output", output: Output}) => { @@ -73,16 +73,14 @@ export abstract class WorkerTask { + private async getWorkerScriptFile(workingFolder: string = os.tmpdir()): Promise { /* istanbul ignore if */ - if (this.workerScriptFile !== undefined) { - return this.workerScriptFile; + if (this.workerScriptFileCache.has(workingFolder)) { + return this.workerScriptFileCache.get(workingFolder)!; } - const tempDir: string = await createTempDir(); - // We must use common JS since the taskJsFilePath points to a transpiled common JS file - this.workerScriptFile = path.join(tempDir, `${this.taskClassName}_worker_script.cjs`); + const workerScriptFile = path.join(workingFolder, `${this.taskClassName}_worker_script.cjs`); const workerScriptFileContents: string = `const { parentPort, workerData } = require("node:worker_threads");\n` + `const engineApi = require("${require.resolve("@salesforce/code-analyzer-engine-api").replace(/\\/g, '\\\\')}");\n` + @@ -100,7 +98,8 @@ export abstract class WorkerTask { engine.onEvent(EventType.TelemetryEvent, (e: TelemetryEvent) => telemetryEvents.push(e)); engine.onEvent(EventType.LogEvent, (e: LogEvent) => logEvents.push(e)); const workspace: Workspace = new Workspace('id', [path.resolve('.')]); - const ruleDescriptions: RuleDescription[] = await engine.describeRules({logFolder: os.tmpdir(), workspace: workspace}); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions(workspace)); const recommendedRuleNames: string[] = ruleDescriptions.filter(rd => rd.tags.includes('Recommended')).map(rd => rd.name); - const engineRunResults: EngineRunResults = await engine.runRules(recommendedRuleNames, {logFolder: os.tmpdir(), workspace: workspace}); + const engineRunResults: EngineRunResults = await engine.runRules(recommendedRuleNames, createRunOptions(workspace)); const violationsFromJsFile: Violation[] = engineRunResults.violations.filter(v => path.extname(v.codeLocations[0].file) === '.js'); expect(violationsFromJsFile).toHaveLength(3); @@ -93,9 +93,9 @@ describe('End to end test', () => { engine.onEvent(EventType.LogEvent, (e: LogEvent) => logEvents.push(e)); engine.onEvent(EventType.TelemetryEvent, (e: TelemetryEvent) => telemetryEvents.push(e)); const workspace: Workspace = new Workspace('id', [path.resolve('.')]); - const ruleDescriptions: RuleDescription[] = await engine.describeRules({logFolder: os.tmpdir(), workspace: workspace}); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions(workspace)); const recommendedRuleNames: string[] = ruleDescriptions.filter(rd => rd.tags.includes('Recommended')).map(rd => rd.name); - const engineRunResults: EngineRunResults = await engine.runRules(recommendedRuleNames, {logFolder: os.tmpdir(), workspace: workspace}); + const engineRunResults: EngineRunResults = await engine.runRules(recommendedRuleNames, createRunOptions(workspace)); const violationsFromJsFile: Violation[] = engineRunResults.violations.filter(v => path.extname(v.codeLocations[0].file) === '.js'); expect(violationsFromJsFile).toHaveLength(3); @@ -130,4 +130,4 @@ describe('End to end test', () => { } }); }); -}); +}); \ No newline at end of file diff --git a/packages/code-analyzer-eslint-engine/test/engine.test.ts b/packages/code-analyzer-eslint-engine/test/engine.test.ts index 295f8e0b..bcf37b80 100644 --- a/packages/code-analyzer-eslint-engine/test/engine.test.ts +++ b/packages/code-analyzer-eslint-engine/test/engine.test.ts @@ -1,6 +1,5 @@ import { ConfigObject, - DescribeOptions, DescribeRulesProgressEvent, Engine, EngineRunResults, @@ -13,17 +12,17 @@ import { Violation, Workspace } from "@salesforce/code-analyzer-engine-api"; -import fs from "node:fs"; -import path from "node:path"; -import process from "node:process"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as process from "node:process"; import {DEFAULT_CONFIG, ESLintEngineConfig} from "../src/config"; import {getMessage} from "../src/messages"; import * as os from "node:os"; import {ESLintEnginePlugin} from "../src"; import {ESLintEngine} from "../src/engine"; -import {unzipToFolder} from "./test-helpers"; +import {createDescribeOptions, createRunOptions, unzipToFolder} from "./test-helpers"; -jest.setTimeout(30_000); +jest.setTimeout(60_000); const DEFAULT_CONFIG_FOR_TESTING: ESLintEngineConfig = { ...DEFAULT_CONFIG, @@ -102,7 +101,7 @@ describe('Tests for the describeRules method of ESLintEngine', () => { config_root: __dirname, auto_discover_eslint_config: true, }); - const ruleDescriptions: RuleDescription[] = await engine.describeRules({logFolder: os.tmpdir()}); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); expect(ruleDescriptions).toEqual(caseObj.expectationRuleDescriptions); } finally { process.chdir(origWorkingDir); @@ -818,20 +817,6 @@ function makeUniqueAndSorted(ruleDescriptions: RuleDescription[]): RuleDescripti .sort((r1, r2) => r1.name.localeCompare((r2.name))); } -function createDescribeOptions(workspace?: Workspace): DescribeOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} - -function createRunOptions(workspace: Workspace): RunOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} - async function createEngineFromPlugin(configObject: ConfigObject): Promise { const plugin: ESLintEnginePlugin = new ESLintEnginePlugin(); const engine: ESLintEngine = await plugin.createEngine('eslint', configObject); diff --git a/packages/code-analyzer-eslint-engine/test/rule-mappings.test.ts b/packages/code-analyzer-eslint-engine/test/rule-mappings.test.ts index 4d615de7..c6673d1e 100644 --- a/packages/code-analyzer-eslint-engine/test/rule-mappings.test.ts +++ b/packages/code-analyzer-eslint-engine/test/rule-mappings.test.ts @@ -1,14 +1,14 @@ import {Engine, RuleDescription} from "@salesforce/code-analyzer-engine-api"; import {RULE_MAPPINGS} from "../src/rule-mappings"; import {DEFAULT_CONFIG} from "../src/config"; -import * as os from "node:os"; import {ESLintEnginePlugin} from "../src"; +import { createDescribeOptions } from "./test-helpers"; describe('Tests for the rule-mappings', () => { it('Test that the list of all bundled rules matches our RULE_MAPPINGS list', async () => { const enginePlugin: ESLintEnginePlugin = new ESLintEnginePlugin(); const engine: Engine = await enginePlugin.createEngine('eslint', DEFAULT_CONFIG); - const ruleDescriptions: RuleDescription[] = await engine.describeRules({logFolder: os.tmpdir()}); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); const actualRuleNames: Set = new Set(ruleDescriptions.map(rd => rd.name)); const ruleNamesInRuleMappings: Set = new Set(Object.keys(RULE_MAPPINGS)); @@ -34,4 +34,4 @@ function setDiff(setA: Set, setB: Set): Set { return new Set( [...setA].filter(value => !setB.has(value)) ); -} +} \ No newline at end of file diff --git a/packages/code-analyzer-eslint-engine/test/test-helpers.ts b/packages/code-analyzer-eslint-engine/test/test-helpers.ts index 56530a55..c1554a0a 100644 --- a/packages/code-analyzer-eslint-engine/test/test-helpers.ts +++ b/packages/code-analyzer-eslint-engine/test/test-helpers.ts @@ -1,5 +1,8 @@ -import process from "node:process"; -import path from "node:path"; +import { DescribeOptions, RunOptions, Workspace } from "@salesforce/code-analyzer-engine-api"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as process from "node:process"; import * as unzipper from "unzipper"; export function changeWorkingDirectoryToPackageRoot() { @@ -22,3 +25,19 @@ export async function unzipToFolder(zipFile: string, outputFolder: string): Prom const directory: unzipper.CentralDirectory = await unzipper.Open.file(zipFile); await directory.extract({path: outputFolder}); } + +export function createDescribeOptions(workspace?: Workspace): DescribeOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} + +export function createRunOptions(workspace: Workspace): RunOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} diff --git a/packages/code-analyzer-eslint-engine/tsconfig.json b/packages/code-analyzer-eslint-engine/tsconfig.json index 8f5b9142..111c54ac 100644 --- a/packages/code-analyzer-eslint-engine/tsconfig.json +++ b/packages/code-analyzer-eslint-engine/tsconfig.json @@ -1,10 +1,8 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "composite": true, - "outDir": "./dist", "rootDir": ".", - "isolatedModules": true + "noEmit": true }, "include": [ "./src", diff --git a/packages/code-analyzer-eslint8-engine/package.json b/packages/code-analyzer-eslint8-engine/package.json index c77ffe20..bc4201c8 100644 --- a/packages/code-analyzer-eslint8-engine/package.json +++ b/packages/code-analyzer-eslint8-engine/package.json @@ -18,7 +18,7 @@ "@eslint/js": "8.57.1", "@lwc/eslint-plugin-lwc": "2.2.0", "@lwc/eslint-plugin-lwc-platform": "5.2.0", - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@salesforce/eslint-config-lwc": "3.7.2", "@salesforce/eslint-plugin-lightning": "1.0.1", "@types/node": "^20.0.0", @@ -51,7 +51,7 @@ ], "scripts": { "build": "tsc --build tsconfig.build.json --verbose", - "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage", + "test": "tsc --build tsconfig.json && cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage", "lint": "eslint src/**/*.ts", "package": "npm pack", "all": "npm run build && npm run lint && npm run test && npm run package", diff --git a/packages/code-analyzer-eslint8-engine/test/end-to-end.test.ts b/packages/code-analyzer-eslint8-engine/test/end-to-end.test.ts index cb0b1503..49f6d2f6 100644 --- a/packages/code-analyzer-eslint8-engine/test/end-to-end.test.ts +++ b/packages/code-analyzer-eslint8-engine/test/end-to-end.test.ts @@ -9,9 +9,8 @@ import { Violation, Workspace } from "@salesforce/code-analyzer-engine-api"; -import path from "node:path"; -import {changeWorkingDirectoryToPackageRoot} from "./test-helpers"; -import * as os from "node:os"; +import * as path from "node:path"; +import {changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions} from "./test-helpers"; changeWorkingDirectoryToPackageRoot(); @@ -33,9 +32,9 @@ describe('End to end test', () => { const workspace: Workspace = new Workspace('id', [ path.resolve('test', 'test-data', 'legacyConfigCases', 'workspace_NoCustomConfig') ]); - const ruleDescriptions: RuleDescription[] = await engine.describeRules({logFolder: os.tmpdir(), workspace: workspace}); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions(workspace)); const recommendedRuleNames: string[] = ruleDescriptions.filter(rd => rd.tags.includes('Recommended')).map(rd => rd.name); - const engineRunResults: EngineRunResults = await engine.runRules(recommendedRuleNames, {logFolder: os.tmpdir(), workspace: workspace}); + const engineRunResults: EngineRunResults = await engine.runRules(recommendedRuleNames, createRunOptions(workspace)); const violationsFromJsFile: Violation[] = engineRunResults.violations.filter(v => path.extname(v.codeLocations[0].file) === '.js'); expect(violationsFromJsFile).toHaveLength(3); @@ -51,4 +50,4 @@ describe('End to end test', () => { 'no-invalid-regexp' ])); }); -}); +}); \ No newline at end of file diff --git a/packages/code-analyzer-eslint8-engine/test/engine.test.ts b/packages/code-analyzer-eslint8-engine/test/engine.test.ts index 58abbfd8..fd3ddbb0 100644 --- a/packages/code-analyzer-eslint8-engine/test/engine.test.ts +++ b/packages/code-analyzer-eslint8-engine/test/engine.test.ts @@ -10,9 +10,9 @@ import { Violation, Workspace, DescribeOptions } from "@salesforce/code-analyzer-engine-api"; -import {changeWorkingDirectoryToPackageRoot, unzipToFolder} from "./test-helpers"; -import fs from "node:fs"; -import path from "node:path"; +import {changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions, unzipToFolder} from "./test-helpers"; +import * as fs from "node:fs"; +import * as path from "node:path"; import process from "node:process"; import {ESLint8Engine} from "../src/engine"; import {DEFAULT_CONFIG} from "../src/config"; @@ -21,7 +21,7 @@ import * as os from "node:os"; changeWorkingDirectoryToPackageRoot(); -jest.setTimeout(30_000); +jest.setTimeout(60_000); const legacyConfigCasesFolder: string = path.join(__dirname, 'test-data', 'legacyConfigCases'); const workspaceWithNoCustomConfig: string = @@ -75,7 +75,7 @@ describe('Tests for the describeRules method of ESLint8Engine', () => { const engine: ESLint8Engine = new ESLint8Engine({...DEFAULT_CONFIG, auto_discover_eslint_config: true }); - const ruleDescriptions: RuleDescription[] = await engine.describeRules({logFolder: os.tmpdir()}); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); expect(ruleDescriptions).toEqual(caseObj.expectationRuleDescriptions); } finally { process.chdir(origWorkingDir); @@ -687,18 +687,4 @@ function loadRuleDescriptions(fileNameFromLegacyConfigCasesFolder: string): Rule function makeUniqueAndSorted(ruleDescriptions: RuleDescription[]): RuleDescription[] { return Array.from(new Map(ruleDescriptions.map(rule => [rule.name, rule])).values()) .sort((r1, r2) => r1.name.localeCompare((r2.name))); -} - -function createDescribeOptions(workspace?: Workspace): DescribeOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} - -function createRunOptions(workspace: Workspace): RunOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} +} \ No newline at end of file diff --git a/packages/code-analyzer-eslint8-engine/test/rule-mappings.test.ts b/packages/code-analyzer-eslint8-engine/test/rule-mappings.test.ts index 8c57895c..38cc0f96 100644 --- a/packages/code-analyzer-eslint8-engine/test/rule-mappings.test.ts +++ b/packages/code-analyzer-eslint8-engine/test/rule-mappings.test.ts @@ -2,12 +2,12 @@ import {RuleDescription} from "@salesforce/code-analyzer-engine-api"; import {RULE_MAPPINGS} from "../src/rule-mappings"; import {ESLint8Engine} from "../src/engine"; import {DEFAULT_CONFIG} from "../src/config"; -import * as os from "node:os"; +import { createDescribeOptions } from "./test-helpers"; describe('Tests for the rule-mappings', () => { it('Test that the list of all bundled rules matches our RULE_MAPPINGS list', async () => { const engine: ESLint8Engine = new ESLint8Engine(DEFAULT_CONFIG); - const ruleDescriptions: RuleDescription[] = await engine.describeRules({logFolder: os.tmpdir()}); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); const actualRuleNames: Set = new Set(ruleDescriptions.map(rd => rd.name)); const ruleNamesInRuleMappings: Set = new Set(Object.keys(RULE_MAPPINGS)); @@ -33,4 +33,4 @@ function setDiff(setA: Set, setB: Set): Set { return new Set( [...setA].filter(value => !setB.has(value)) ); -} +} \ No newline at end of file diff --git a/packages/code-analyzer-eslint8-engine/test/test-helpers.ts b/packages/code-analyzer-eslint8-engine/test/test-helpers.ts index 58103403..c1554a0a 100644 --- a/packages/code-analyzer-eslint8-engine/test/test-helpers.ts +++ b/packages/code-analyzer-eslint8-engine/test/test-helpers.ts @@ -1,5 +1,8 @@ -import process from "node:process"; -import path from "node:path"; +import { DescribeOptions, RunOptions, Workspace } from "@salesforce/code-analyzer-engine-api"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as process from "node:process"; import * as unzipper from "unzipper"; export function changeWorkingDirectoryToPackageRoot() { @@ -21,4 +24,20 @@ export function changeWorkingDirectoryToPackageRoot() { export async function unzipToFolder(zipFile: string, outputFolder: string): Promise { const directory: unzipper.CentralDirectory = await unzipper.Open.file(zipFile); await directory.extract({path: outputFolder}); -} \ No newline at end of file +} + +export function createDescribeOptions(workspace?: Workspace): DescribeOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} + +export function createRunOptions(workspace: Workspace): RunOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} diff --git a/packages/code-analyzer-eslint8-engine/tsconfig.build.json b/packages/code-analyzer-eslint8-engine/tsconfig.build.json index f839019c..69d7a287 100644 --- a/packages/code-analyzer-eslint8-engine/tsconfig.build.json +++ b/packages/code-analyzer-eslint8-engine/tsconfig.build.json @@ -13,4 +13,4 @@ "path": "../code-analyzer-engine-api/tsconfig.build.json" } ] - } +} diff --git a/packages/code-analyzer-eslint8-engine/tsconfig.json b/packages/code-analyzer-eslint8-engine/tsconfig.json index 8f5b9142..111c54ac 100644 --- a/packages/code-analyzer-eslint8-engine/tsconfig.json +++ b/packages/code-analyzer-eslint8-engine/tsconfig.json @@ -1,10 +1,8 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "composite": true, - "outDir": "./dist", "rootDir": ".", - "isolatedModules": true + "noEmit": true }, "include": [ "./src", diff --git a/packages/code-analyzer-flow-engine/package.json b/packages/code-analyzer-flow-engine/package.json index 636673c8..603b3605 100644 --- a/packages/code-analyzer-flow-engine/package.json +++ b/packages/code-analyzer-flow-engine/package.json @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -39,7 +39,7 @@ ], "scripts": { "build": "tsc --build tsconfig.build.json --verbose", - "test": "jest --coverage", + "test": "tsc --build tsconfig.json && jest --coverage", "lint": "eslint src/**/*.ts", "package": "npm pack", "all": "npm run build && && npm run lint npm run test && npm run package", diff --git a/packages/code-analyzer-flow-engine/src/engine.ts b/packages/code-analyzer-flow-engine/src/engine.ts index c47eb26a..0171316a 100644 --- a/packages/code-analyzer-flow-engine/src/engine.ts +++ b/packages/code-analyzer-flow-engine/src/engine.ts @@ -82,6 +82,7 @@ export class FlowScannerEngine extends Engine { } const executionResults: FlowScannerExecutionResult = await this.commandWrapper.runFlowScannerRules( + runOptions.workingFolder, workspaceFlows, targetedFlows, logFile, diff --git a/packages/code-analyzer-flow-engine/src/python/FlowScannerCommandWrapper.ts b/packages/code-analyzer-flow-engine/src/python/FlowScannerCommandWrapper.ts index 9d715f3f..d9b6371f 100644 --- a/packages/code-analyzer-flow-engine/src/python/FlowScannerCommandWrapper.ts +++ b/packages/code-analyzer-flow-engine/src/python/FlowScannerCommandWrapper.ts @@ -1,4 +1,3 @@ -import {createTempDir} from '@salesforce/code-analyzer-engine-api/utils'; import {PythonCommandExecutor} from './PythonCommandExecutor'; import {getMessage} from '../messages'; import path from "node:path"; @@ -6,6 +5,7 @@ import fs from "node:fs"; export interface FlowScannerCommandWrapper { runFlowScannerRules( + workingFolder: string, workspaceFlowFiles: string[], targetedFlowFiles: string[], absLogFilePath: string, @@ -47,18 +47,18 @@ export class RunTimeFlowScannerCommandWrapper implements FlowScannerCommandWrapp } public async runFlowScannerRules( + workingFolder: string, workspaceFlowFiles: string[], targetedFlowFiles: string[], absLogFilePath: string, completionPercentageHandler: (percentage: number) => void ): Promise { - const tempDir: string = await createTempDir(); - const workspaceFlowsFile: string = path.join(tempDir, 'workspaceFiles.txt'); - const targetedFlowsFile: string = path.join(tempDir, 'targetedFiles.txt'); + const workspaceFlowsFile: string = path.join(workingFolder, 'workspaceFiles.txt'); + const targetedFlowsFile: string = path.join(workingFolder, 'targetedFiles.txt'); await fs.promises.writeFile(workspaceFlowsFile, workspaceFlowFiles.join('\n'), 'utf-8'); await fs.promises.writeFile(targetedFlowsFile, targetedFlowFiles.join('\n'), 'utf-8'); - const flowScannerResultsFile: string = path.join(tempDir, 'flowScannerResultsFile.json') + const flowScannerResultsFile: string = path.join(workingFolder, 'flowScannerResultsFile.json') const commandName = 'flowtest'; //pythonModuleName set by internal team const pythonArgs: string[] = [ diff --git a/packages/code-analyzer-flow-engine/test/engine.test.ts b/packages/code-analyzer-flow-engine/test/engine.test.ts index 269a773c..feecdaae 100644 --- a/packages/code-analyzer-flow-engine/test/engine.test.ts +++ b/packages/code-analyzer-flow-engine/test/engine.test.ts @@ -1,6 +1,6 @@ -import path from 'node:path'; +import * as path from "node:path"; +import * as os from "node:os"; import { - DescribeOptions, DescribeRulesProgressEvent, EngineRunResults, EventType, @@ -16,10 +16,9 @@ import { import {FixedClock} from "@salesforce/code-analyzer-engine-api/utils"; import {FlowScannerEngine} from "../src/engine"; import {RunTimeFlowScannerCommandWrapper} from "../src/python/FlowScannerCommandWrapper"; -import {changeWorkingDirectoryToPackageRoot} from "./test-helpers"; -import os from "node:os"; +import {changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions} from "./test-helpers"; import {getMessage} from "../src/messages"; -import fs from "node:fs"; +import * as fs from "node:fs"; changeWorkingDirectoryToPackageRoot(); @@ -41,13 +40,10 @@ const ALL_FLOW_RULES: string[] = [ 'PreventPassingUserDataIntoElementWithoutSharing' ]; +jest.setTimeout(60_000); + describe('Tests for the FlowScannerEngine', () => { const flowScannerCommandWrapper: RunTimeFlowScannerCommandWrapper = new RunTimeFlowScannerCommandWrapper('python3'); - let tempFolder: string; - - beforeAll(async() => { - tempFolder = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'flow-engine-test')); - }); it('getName() returns correct name', () => { const engine: FlowScannerEngine = new FlowScannerEngine(flowScannerCommandWrapper); @@ -68,15 +64,21 @@ describe('Tests for the FlowScannerEngine', () => { engine.onEvent(EventType.LogEvent, (e: LogEvent)=> logEvents.push(e)); // Part 1: Describing production rules. - const ruleDescriptors: RuleDescription[] = await engine.describeRules(createDescribeOptions(tempFolder, workspace)); + const ruleDescriptors: RuleDescription[] = await engine.describeRules(createDescribeOptions(workspace)); // No need to do in-depth examination of the rules, since other tests already do that. Just make sure we got // the right number of rules. expect(ruleDescriptors).toHaveLength(2); expect(describeProgressEvents.map(e => e.percentComplete)).toEqual([0, 75, 100]); // Part 2: Running production rules. - const results: EngineRunResults = await engine.runRules(ruleDescriptors.map(r => r.name), - createRunOptions(tempFolder, workspace)); + const runOptions: RunOptions = createRunOptions(workspace); + const expectedFlowLogFile: string = path.join(runOptions.logFolder, 'sfca-flow-2025_02_20_14_30_18_014.log'); + if (fs.existsSync(expectedFlowLogFile)) { + // Since we are fixing the timestamp, we should make sure that before we run the file doesn't exist, to + // properly test that the runRules makes it exist. + fs.rmSync(expectedFlowLogFile); + } + const results: EngineRunResults = await engine.runRules(ruleDescriptors.map(r => r.name), runOptions); // No need to do in-depth examination of the results, since other tests already do that. Just make sure we // got the right number of violations. expect(results.violations).toHaveLength(7); @@ -85,7 +87,6 @@ describe('Tests for the FlowScannerEngine', () => { // Confirm separate flow log file exists and the main log points to this file const debugLogMsgs: string[] = logEvents.filter(e => e.logLevel == LogLevel.Debug).map(e => e.message); expect(debugLogMsgs).toHaveLength(1); - const expectedFlowLogFile: string = path.join(tempFolder, 'sfca-flow-2025_02_20_14_30_18_014.log'); expect(debugLogMsgs[0]).toEqual(getMessage('WritingFlowLogToFile', expectedFlowLogFile)); const flowLogContents: string = await fs.promises.readFile(expectedFlowLogFile, 'utf-8'); expect(flowLogContents).toContain('DEBUG'); // Sanity check that we are using --debug log level @@ -98,7 +99,7 @@ describe('Tests for the FlowScannerEngine', () => { it('Consolidates well-formed Flow Scanner rule descriptors into Code Analyzer rule descriptors', async () => { const engine: FlowScannerEngine = new FlowScannerEngine(flowScannerCommandWrapper); - const ruleDescriptors: RuleDescription[] = await engine.describeRules(createDescribeOptions(tempFolder)); + const ruleDescriptors: RuleDescription[] = await engine.describeRules(createDescribeOptions()); expect(ruleDescriptors).toHaveLength(2); expect(ruleDescriptors[0]).toEqual({ @@ -136,7 +137,7 @@ describe('Tests for the FlowScannerEngine', () => { ])('When workspace $desc, rules are returned', async ({workspace}) => { const engine: FlowScannerEngine = new FlowScannerEngine(flowScannerCommandWrapper); - const ruleDescriptors: RuleDescription[] = await engine.describeRules(createDescribeOptions(tempFolder, workspace)); + const ruleDescriptors: RuleDescription[] = await engine.describeRules(createDescribeOptions(workspace)); expect(ruleDescriptors).toHaveLength(2); }); @@ -153,7 +154,7 @@ describe('Tests for the FlowScannerEngine', () => { ])('When workspace $desc, no rules are returned', async ({workspace}) => { const engine: FlowScannerEngine = new FlowScannerEngine(flowScannerCommandWrapper); - const ruleDescriptors: RuleDescription[] = await engine.describeRules(createDescribeOptions(tempFolder, workspace)); + const ruleDescriptors: RuleDescription[] = await engine.describeRules(createDescribeOptions(workspace)); expect(ruleDescriptors).toHaveLength(0); }); @@ -359,7 +360,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When running both rules on workspace that contains violations for SystemModeWithoutSharing and SystemModeWithSharing, then results are as expected', async () => { const engineResults: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions( - tempFolder, new Workspace('id', [PATH_TO_MULTIPLE_FLOWS_WORKSPACE]))); + new Workspace('id', [PATH_TO_MULTIPLE_FLOWS_WORKSPACE]))); expect(engineResults.violations).toHaveLength(7); expect(engineResults.violations).toContainEqual(expectedExample1Violation1); @@ -376,7 +377,7 @@ describe('Tests for the FlowScannerEngine', () => { const selectedRuleNames: string[] = ['PreventPassingUserDataIntoElementWithSharing']; const engineResults: EngineRunResults = await engine.runRules(selectedRuleNames, createRunOptions( - tempFolder, new Workspace('id', [path.resolve(__dirname, 'test-data', 'example workspaces')], + new Workspace('id', [path.resolve(__dirname, 'test-data', 'example workspaces')], [PATH_TO_MULTIPLE_FLOWS_WORKSPACE]))); expect(engineResults.violations).toHaveLength(2); @@ -388,7 +389,7 @@ describe('Tests for the FlowScannerEngine', () => { const engine: FlowScannerEngine = new FlowScannerEngine(flowScannerCommandWrapper); const engineResults: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions( - tempFolder, new Workspace('id', [PATH_TO_EXAMPLE2]))); + new Workspace('id', [PATH_TO_EXAMPLE2]))); expect(engineResults.violations).toHaveLength(2); expect(engineResults.violations).toContainEqual(expectedExample2Violation1); @@ -397,7 +398,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When workspace does not contain flow files, then return zero violations', async () => { const engineResults: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions( - tempFolder, new Workspace('id', [PATH_TO_NO_FLOWS_WORKSPACE]))); + new Workspace('id', [PATH_TO_NO_FLOWS_WORKSPACE]))); expect(engineResults.violations).toHaveLength(0); }); @@ -406,7 +407,7 @@ describe('Tests for the FlowScannerEngine', () => { const engine: FlowScannerEngine = new FlowScannerEngine(flowScannerCommandWrapper); const engineResults: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions( - tempFolder, new Workspace('id', [PATH_TO_EXAMPLE3]))); + new Workspace('id', [PATH_TO_EXAMPLE3]))); expect(engineResults.violations).toHaveLength(0); }); @@ -415,7 +416,7 @@ describe('Tests for the FlowScannerEngine', () => { const engine: FlowScannerEngine = new FlowScannerEngine(flowScannerCommandWrapper); const engineResults: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions( - tempFolder, new Workspace('id', [PATH_TO_MULTIPLE_FLOWS_WORKSPACE], [PATH_TO_EXAMPLE3]))); + new Workspace('id', [PATH_TO_MULTIPLE_FLOWS_WORKSPACE], [PATH_TO_EXAMPLE3]))); expect(engineResults.violations).toHaveLength(0); }); @@ -424,7 +425,7 @@ describe('Tests for the FlowScannerEngine', () => { const engine: FlowScannerEngine = new FlowScannerEngine(flowScannerCommandWrapper); const engineResults: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions( - tempFolder, new Workspace('id', [PATH_TO_MULTIPLE_FLOWS_WORKSPACE], [PATH_TO_EXAMPLE1]))); + new Workspace('id', [PATH_TO_MULTIPLE_FLOWS_WORKSPACE], [PATH_TO_EXAMPLE1]))); expect(engineResults.violations).toHaveLength(2); expect(engineResults.violations).toContainEqual(expectedExample1Violation1); @@ -438,7 +439,7 @@ describe('Tests for the FlowScannerEngine', () => { const engine: FlowScannerEngine = new FlowScannerEngine(flowScannerCommandWrapper); const engineResults: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions( - tempFolder, new Workspace('id', [PATH_TO_ONE_FLOW_NO_VIOLATIONS_WORKSPACE]))); + new Workspace('id', [PATH_TO_ONE_FLOW_NO_VIOLATIONS_WORKSPACE]))); expect(engineResults.violations).toHaveLength(0); }); @@ -449,7 +450,7 @@ describe('Tests for the FlowScannerEngine', () => { ])('When workspace contains a parent flow but not its child subflow, then return valid results with zero violations', async (workspace) => { const engine: FlowScannerEngine = new FlowScannerEngine(flowScannerCommandWrapper); - const engineResults1: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const engineResults1: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(engineResults1.violations).toHaveLength(0); }); @@ -459,7 +460,7 @@ describe('Tests for the FlowScannerEngine', () => { ])('When workspace contains a child subflow but not its parent flow, then return valid results with zero violations', async (workspace) => { const engine: FlowScannerEngine = new FlowScannerEngine(flowScannerCommandWrapper); - const engineResults1: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const engineResults1: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(engineResults1.violations).toHaveLength(0); }); @@ -508,7 +509,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When both parent and child are in workspace and targeted, then expect violation', async () => { const workspace: Workspace = new Workspace("someId", [PARENT_WITH_SOURCE_CALLS_SUB_WITH_SINK_WORKSPACE]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(1); expect(results.violations[0]).toEqual(expectedViolation); @@ -523,7 +524,7 @@ describe('Tests for the FlowScannerEngine', () => { PATH_TO_ONE_FLOW_NO_VIOLATIONS_WORKSPACE ]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(0); }); @@ -531,7 +532,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When both parent and child are in workspace but only child is targeted, then expect no violation', async () => { const workspace: Workspace = new Workspace("someId", [PARENT_WITH_SOURCE_CALLS_SUB_WITH_SINK_WORKSPACE], [childFlowFile]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(0); }); @@ -539,7 +540,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When both parent and child are in workspace but only parent is targeted, then expect violation since sink is found in workspace', async () => { const workspace: Workspace = new Workspace("someId", [PARENT_WITH_SOURCE_CALLS_SUB_WITH_SINK_WORKSPACE], [parentFlowFile]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(1); expect(results.violations[0]).toEqual(expectedViolation); @@ -548,7 +549,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When only parent is in workspace and only parent is targeted, then expect no violation since sink is not in workspace', async () => { const workspace: Workspace = new Workspace("someId", [parentFlowFile], [parentFlowFile]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(0); }); @@ -556,7 +557,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When only child is in workspace and only child is targeted, then expect no violation since source is not in workspace', async () => { const workspace: Workspace = new Workspace("someId", [childFlowFile], [childFlowFile]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(0); }); @@ -607,7 +608,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When both parent and child are in workspace and targeted, then expect violation', async () => { const workspace: Workspace = new Workspace("someId", [PARENT_WITH_SINK_CALLS_SUB_WITH_SOURCE_WORKSPACE]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(1); expect(results.violations[0]).toEqual(expectedViolation); @@ -616,7 +617,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When both parent and child are in workspace but neither are targeted, then expect no violation', async () => { const workspace: Workspace = new Workspace("someId", [parentFlowFile, childFlowFile, PATH_TO_EXAMPLE3], [PATH_TO_EXAMPLE3]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(0); }); @@ -624,7 +625,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When both parent and child are in workspace but only child is targeted, then expect no violation since child would not be called', async () => { const workspace: Workspace = new Workspace("someId", [PARENT_WITH_SINK_CALLS_SUB_WITH_SOURCE_WORKSPACE], [childFlowFile]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(0); }); @@ -632,7 +633,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When both parent and child are in workspace but only parent is targeted, then expect a violation since the parent is targeted', async () => { const workspace: Workspace = new Workspace("someId", [PARENT_WITH_SINK_CALLS_SUB_WITH_SOURCE_WORKSPACE], [parentFlowFile]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(1); expect(results.violations[0]).toEqual(expectedViolation); @@ -641,7 +642,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When only parent is in workspace and only parent is targeted, then expect no violation since sink is not in workspace', async () => { const workspace: Workspace = new Workspace("someId", [parentFlowFile], [parentFlowFile]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(0); }); @@ -649,7 +650,7 @@ describe('Tests for the FlowScannerEngine', () => { it('When only child is in workspace and only child is targeted, then expect no violation since source is not in workspace', async () => { const workspace: Workspace = new Workspace("someId", [childFlowFile], [childFlowFile]); - const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(tempFolder, workspace)); + const results: EngineRunResults = await engine.runRules(ALL_FLOW_RULES, createRunOptions(workspace)); expect(results.violations).toHaveLength(0); }); @@ -669,18 +670,4 @@ describe('Tests for the FlowScannerEngine', () => { }); }); }); -}); - -function createDescribeOptions(logFolder: string, workspace?: Workspace): DescribeOptions { - return { - logFolder: logFolder, - workspace: workspace - } -} - -function createRunOptions(logFolder: string, workspace: Workspace): RunOptions { - return { - logFolder: logFolder, - workspace: workspace - } -} +}); \ No newline at end of file diff --git a/packages/code-analyzer-flow-engine/test/python/FlowScannerCommandWrapper.test.ts b/packages/code-analyzer-flow-engine/test/python/FlowScannerCommandWrapper.test.ts index 4a50ccc0..15d0af51 100644 --- a/packages/code-analyzer-flow-engine/test/python/FlowScannerCommandWrapper.test.ts +++ b/packages/code-analyzer-flow-engine/test/python/FlowScannerCommandWrapper.test.ts @@ -10,13 +10,16 @@ const PATH_TO_MULTIPLE_FLOWS_WORKSPACE = path.resolve(__dirname, '..', 'test-dat const PATH_TO_EXAMPLE1: string = path.join(PATH_TO_MULTIPLE_FLOWS_WORKSPACE, 'example1_containsWithoutSharingViolations.flow-meta.xml'); const PATH_TO_EXAMPLE2: string = path.join(PATH_TO_MULTIPLE_FLOWS_WORKSPACE, 'example2_containsWithSharingViolations.flow'); +jest.setTimeout(60_000); + describe('FlowScannerCommandWrapper implementations', () => { describe('RunTimeFlowScannerCommandWrapper', () => { + let workingFolder: string; let tempLogFile: string; - beforeAll(async() => { - const tempFolder: string = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'engine-test')); - tempLogFile = path.join(tempFolder, "flow_scanner_logfile.log"); + beforeAll(async () => { + workingFolder = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'engine-test')); + tempLogFile = path.join(workingFolder, "flow_scanner_logfile.log"); }) describe('#runFlowScannerRules()', () => { @@ -30,7 +33,7 @@ describe('FlowScannerCommandWrapper implementations', () => { }; beforeAll(async () => { - results = await wrapper.runFlowScannerRules([PATH_TO_EXAMPLE1, PATH_TO_EXAMPLE2], [PATH_TO_EXAMPLE1, PATH_TO_EXAMPLE2], tempLogFile, statusProcessorFunction); + results = await wrapper.runFlowScannerRules(workingFolder, [PATH_TO_EXAMPLE1, PATH_TO_EXAMPLE2], [PATH_TO_EXAMPLE1, PATH_TO_EXAMPLE2], tempLogFile, statusProcessorFunction); // The `counter` property is irrelevant to us, and causes problems across platforms. So delete it. for (const queryName of Object.keys(results.results)) { for (const queryResults of results.results[queryName]) { @@ -100,7 +103,7 @@ describe('FlowScannerCommandWrapper implementations', () => { }); const wrapper: RunTimeFlowScannerCommandWrapper = new RunTimeFlowScannerCommandWrapper(PYTHON_COMMAND); - await expect(wrapper.runFlowScannerRules([PATH_TO_EXAMPLE1, PATH_TO_EXAMPLE2], [PATH_TO_EXAMPLE1, PATH_TO_EXAMPLE2], tempLogFile, (_num: number) => {})) + await expect(wrapper.runFlowScannerRules(workingFolder, [PATH_TO_EXAMPLE1, PATH_TO_EXAMPLE2], [PATH_TO_EXAMPLE1, PATH_TO_EXAMPLE2], tempLogFile, (_num: number) => {})) .rejects .toThrow(expectedMessage); }); diff --git a/packages/code-analyzer-flow-engine/test/test-helpers.ts b/packages/code-analyzer-flow-engine/test/test-helpers.ts index 4d98690e..6b9bd681 100644 --- a/packages/code-analyzer-flow-engine/test/test-helpers.ts +++ b/packages/code-analyzer-flow-engine/test/test-helpers.ts @@ -1,5 +1,8 @@ -import process from "node:process"; -import path from "node:path"; +import { DescribeOptions, RunOptions, Workspace } from "@salesforce/code-analyzer-engine-api"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as process from "node:process"; export function changeWorkingDirectoryToPackageRoot() { let original_working_directory: string; @@ -16,3 +19,19 @@ export function changeWorkingDirectoryToPackageRoot() { process.chdir(original_working_directory); }); } + +export function createDescribeOptions(workspace?: Workspace): DescribeOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} + +export function createRunOptions(workspace: Workspace): RunOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} diff --git a/packages/code-analyzer-flow-engine/tsconfig.json b/packages/code-analyzer-flow-engine/tsconfig.json index 26bfb1eb..8077e9a0 100644 --- a/packages/code-analyzer-flow-engine/tsconfig.json +++ b/packages/code-analyzer-flow-engine/tsconfig.json @@ -1,10 +1,8 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "composite": true, - "outDir": "./dist", "rootDir": ".", - "isolatedModules": true + "noEmit": true }, "include": [ "./src", diff --git a/packages/code-analyzer-pmd-engine/package.json b/packages/code-analyzer-pmd-engine/package.json index 372106fd..7bfa1e86 100644 --- a/packages/code-analyzer-pmd-engine/package.json +++ b/packages/code-analyzer-pmd-engine/package.json @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -41,7 +41,7 @@ "build-typescript": "tsc --build tsconfig.build.json --verbose", "build": "npm run build-java && npm run build-typescript", "test-java": "sh ./gradlew test jacocoTestReport jacocoTestCoverageVerification", - "test-typescript": "jest --coverage", + "test-typescript": "tsc --build tsconfig.json && jest --coverage", "test": "npm run test-java && npm run test-typescript", "lint": "eslint src/**/*.ts", "package": "npm pack", diff --git a/packages/code-analyzer-pmd-engine/src/cpd-engine.ts b/packages/code-analyzer-pmd-engine/src/cpd-engine.ts index 121384ae..449d97b3 100644 --- a/packages/code-analyzer-pmd-engine/src/cpd-engine.ts +++ b/packages/code-analyzer-pmd-engine/src/cpd-engine.ts @@ -91,7 +91,9 @@ export class CpdEngine extends Engine { this.emitRunRulesProgressEvent(5); - const cpdRunResults: CpdRunResults = await this.cpdWrapperInvoker.invokeRunCommand(inputData, + const cpdRunResults: CpdRunResults = await this.cpdWrapperInvoker.invokeRunCommand( + inputData, + runOptions.workingFolder, (innerPerc: number) => this.emitRunRulesProgressEvent(5 + 93*(innerPerc/100))); // 5 to 98% const violations: Violation[] = []; diff --git a/packages/code-analyzer-pmd-engine/src/cpd-wrapper.ts b/packages/code-analyzer-pmd-engine/src/cpd-wrapper.ts index d4847dd0..ad27d674 100644 --- a/packages/code-analyzer-pmd-engine/src/cpd-wrapper.ts +++ b/packages/code-analyzer-pmd-engine/src/cpd-wrapper.ts @@ -1,5 +1,5 @@ import {getMessageFromCatalog, LogLevel, SHARED_MESSAGE_CATALOG} from "@salesforce/code-analyzer-engine-api"; -import {createTempDir, JavaCommandExecutor} from "@salesforce/code-analyzer-engine-api/utils"; +import {JavaCommandExecutor} from "@salesforce/code-analyzer-engine-api/utils"; import path from "node:path"; import fs from "node:fs"; @@ -45,7 +45,6 @@ export type CpdProcessingError = { export class CpdWrapperInvoker { private readonly javaCommandExecutor: JavaCommandExecutor; - private temporaryWorkingDir?: string; private readonly emitLogEvent: (logLevel: LogLevel, message: string) => void; constructor(javaCommandExecutor: JavaCommandExecutor, emitLogEvent: (logLevel: LogLevel, message: string) => void) { @@ -53,15 +52,14 @@ export class CpdWrapperInvoker { this.emitLogEvent = emitLogEvent; } - async invokeRunCommand(inputData: CpdRunInputData, emitProgress: (percComplete: number) => void): Promise { - const tempDir: string = await this.getTemporaryWorkingDir(); + async invokeRunCommand(inputData: CpdRunInputData, workingFolder: string, emitProgress: (percComplete: number) => void): Promise { emitProgress(5); - const inputFile: string = path.join(tempDir, 'cpdRunInput.json'); + const inputFile: string = path.join(workingFolder, 'cpdRunInput.json'); await fs.promises.writeFile(inputFile, JSON.stringify(inputData), 'utf-8'); emitProgress(10); - const outputFile: string = path.join(tempDir, 'cpdRunOutput.json'); + const outputFile: string = path.join(workingFolder, 'cpdRunOutput.json'); const javaCmdArgs: string[] = [CPD_WRAPPER_JAVA_CLASS, 'run', inputFile, outputFile]; const javaClassPaths: string[] = [ @@ -89,11 +87,4 @@ export class CpdWrapperInvoker { throw new Error(getMessageFromCatalog(SHARED_MESSAGE_CATALOG, 'ErrorParsingOutputFile', outputFile, errMsg), {cause: err}); } } - - private async getTemporaryWorkingDir(): Promise { - if (this.temporaryWorkingDir === undefined) { - this.temporaryWorkingDir = await createTempDir(); - } - return this.temporaryWorkingDir!; - } } \ No newline at end of file diff --git a/packages/code-analyzer-pmd-engine/src/pmd-engine.ts b/packages/code-analyzer-pmd-engine/src/pmd-engine.ts index b702e8cb..93e31eff 100644 --- a/packages/code-analyzer-pmd-engine/src/pmd-engine.ts +++ b/packages/code-analyzer-pmd-engine/src/pmd-engine.ts @@ -62,7 +62,7 @@ export class PmdEngine extends Engine { const workspaceLiaison: WorkspaceLiaison = this.getWorkspaceLiaison(describeOptions.workspace); this.emitDescribeRulesProgressEvent(5); - const ruleInfoList: PmdRuleInfo[] = await this.getPmdRuleInfoList(workspaceLiaison, + const ruleInfoList: PmdRuleInfo[] = await this.getPmdRuleInfoList(workspaceLiaison, describeOptions.workingFolder, (innerPerc: number) => this.emitDescribeRulesProgressEvent(5 + 90*(innerPerc/100))); // 5 to 95% const ruleDescriptions: RuleDescription[] = ruleInfoList.map(toRuleDescription); @@ -76,7 +76,7 @@ export class PmdEngine extends Engine { const relevantLanguageToFilesMap: Map = await workspaceLiaison.getRelevantLanguageToFilesMap(); this.emitRunRulesProgressEvent(2); - const ruleInfoList: PmdRuleInfo[] = await this.getPmdRuleInfoList(workspaceLiaison, + const ruleInfoList: PmdRuleInfo[] = await this.getPmdRuleInfoList(workspaceLiaison, runOptions.workingFolder, (innerPerc: number) => this.emitRunRulesProgressEvent(2 + 3*(innerPerc/100))); // 2 to 5% const selectedRuleInfoList: PmdRuleInfo[] = ruleNames @@ -100,7 +100,10 @@ export class PmdEngine extends Engine { } - const pmdResults: PmdResults = await this.pmdWrapperInvoker.invokeRunCommand(selectedRuleInfoList, runDataPerLanguage, + const pmdResults: PmdResults = await this.pmdWrapperInvoker.invokeRunCommand( + selectedRuleInfoList, + runDataPerLanguage, + runOptions.workingFolder, (innerPerc: number) => this.emitRunRulesProgressEvent(5 + 93*(innerPerc/100))); // 5 to 98% const violations: Violation[] = []; @@ -118,7 +121,9 @@ export class PmdEngine extends Engine { }; } - private async getPmdRuleInfoList(workspaceLiaison: WorkspaceLiaison, emitProgress: (percComplete: number) => void): Promise { + private async getPmdRuleInfoList(workspaceLiaison: WorkspaceLiaison, workingFolder: string, + emitProgress: (percComplete: number) => void): Promise { + const cacheKey: string = getCacheKey(workspaceLiaison.getWorkspace()); if (!this.pmdRuleInfoListCache.has(cacheKey)) { const relevantLanguages: Language[] = await workspaceLiaison.getRelevantLanguages(); @@ -128,7 +133,7 @@ export class PmdEngine extends Engine { ... this.config.custom_rulesets // The user's custom rulesets ] const ruleInfoList: PmdRuleInfo[] = relevantLanguages.length === 0 ? [] : - await this.pmdWrapperInvoker.invokeDescribeCommand(allCustomRulesets, pmdRuleLanguageIds, emitProgress); + await this.pmdWrapperInvoker.invokeDescribeCommand(allCustomRulesets, pmdRuleLanguageIds, workingFolder, emitProgress); this.pmdRuleInfoListCache.set(cacheKey, ruleInfoList); } return this.pmdRuleInfoListCache.get(cacheKey)!; diff --git a/packages/code-analyzer-pmd-engine/src/pmd-wrapper.ts b/packages/code-analyzer-pmd-engine/src/pmd-wrapper.ts index 25d87757..d0b5d735 100644 --- a/packages/code-analyzer-pmd-engine/src/pmd-wrapper.ts +++ b/packages/code-analyzer-pmd-engine/src/pmd-wrapper.ts @@ -1,4 +1,4 @@ -import {createTempDir, JavaCommandExecutor} from "@salesforce/code-analyzer-engine-api/utils"; +import {JavaCommandExecutor} from "@salesforce/code-analyzer-engine-api/utils"; import path from "node:path"; import fs from "node:fs"; import {getMessageFromCatalog, LogLevel, SHARED_MESSAGE_CATALOG} from "@salesforce/code-analyzer-engine-api"; @@ -52,7 +52,6 @@ const STDOUT_ERROR_MARKER = '[Error] '; export class PmdWrapperInvoker { private readonly javaCommandExecutor: JavaCommandExecutor; private readonly userProvidedJavaClasspathEntries: string[]; - private temporaryWorkingDir?: string; private readonly emitLogEvent: (logLevel: LogLevel, message: string) => void; constructor(javaCommandExecutor: JavaCommandExecutor, userProvidedJavaClasspathEntries: string[], emitLogEvent: (logLevel: LogLevel, message: string) => void) { @@ -61,10 +60,11 @@ export class PmdWrapperInvoker { this.emitLogEvent = emitLogEvent; } - async invokeDescribeCommand(customRulesets: string[], pmdRuleLanguageIds: string[], emitProgress: (percComplete: number) => void): Promise { - const tempDir: string = await this.getTemporaryWorkingDir(); - const pmdRulesOutputFile: string = path.join(tempDir, 'ruleInfo.json'); - const customRulesetsListFile: string = path.join(tempDir, 'customRulesetsList.txt'); + async invokeDescribeCommand(customRulesets: string[], pmdRuleLanguageIds: string[], + workingFolder: string, emitProgress: (percComplete: number) => void): Promise { + + const pmdRulesOutputFile: string = path.join(workingFolder, 'ruleInfo.json'); + const customRulesetsListFile: string = path.join(workingFolder, 'customRulesetsList.txt'); await fs.promises.writeFile(customRulesetsListFile, customRulesets.join('\n'), 'utf-8'); emitProgress(10); @@ -95,12 +95,13 @@ export class PmdWrapperInvoker { } } - async invokeRunCommand(pmdRuleInfoList: PmdRuleInfo[], runDataPerLanguage: Record, emitProgress: (percComplete: number) => void): Promise { - const tempDir: string = await this.getTemporaryWorkingDir(); + async invokeRunCommand(pmdRuleInfoList: PmdRuleInfo[], runDataPerLanguage: Record, + workingFolder: string, emitProgress: (percComplete: number) => void): Promise { + emitProgress(2); const ruleSetFileContents: string = createRuleSetFileContentsFor(pmdRuleInfoList); - const ruleSetInputFile: string = path.join(tempDir, 'ruleSetInputFile.xml'); + const ruleSetInputFile: string = path.join(workingFolder, 'ruleSetInputFile.xml'); await fs.promises.writeFile(ruleSetInputFile, ruleSetFileContents, 'utf-8'); emitProgress(6); @@ -109,11 +110,11 @@ export class PmdWrapperInvoker { runDataPerLanguage: runDataPerLanguage } - const inputFile: string = path.join(tempDir, 'pmdRunInput.json'); + const inputFile: string = path.join(workingFolder, 'pmdRunInput.json'); await fs.promises.writeFile(inputFile, JSON.stringify(inputData), 'utf-8'); emitProgress(10); - const resultsOutputFile: string = path.join(tempDir, 'resultsFile.json'); + const resultsOutputFile: string = path.join(workingFolder, 'resultsFile.json'); const javaCmdArgs: string[] = [PMD_WRAPPER_JAVA_CLASS, 'run', inputFile, resultsOutputFile]; const javaClassPaths: string[] = [ path.join(PMD_WRAPPER_LIB_FOLDER, '*'), @@ -142,13 +143,6 @@ export class PmdWrapperInvoker { throw new Error(getMessageFromCatalog(SHARED_MESSAGE_CATALOG, 'ErrorParsingOutputFile', resultsOutputFile, errMsg), {cause: err}); } } - - private async getTemporaryWorkingDir(): Promise { - if (this.temporaryWorkingDir === undefined) { - this.temporaryWorkingDir = await createTempDir(); - } - return this.temporaryWorkingDir!; - } } function createRuleSetFileContentsFor(pmdRuleInfoList: PmdRuleInfo[]): string { diff --git a/packages/code-analyzer-pmd-engine/test/cpd-engine.test.ts b/packages/code-analyzer-pmd-engine/test/cpd-engine.test.ts index c945a8db..8b8832de 100644 --- a/packages/code-analyzer-pmd-engine/test/cpd-engine.test.ts +++ b/packages/code-analyzer-pmd-engine/test/cpd-engine.test.ts @@ -1,11 +1,9 @@ -import {changeWorkingDirectoryToPackageRoot} from "./test-helpers"; +import {changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions} from "./test-helpers"; import { - DescribeOptions, DescribeRulesProgressEvent, EngineRunResults, EventType, RuleDescription, - RunOptions, RunRulesProgressEvent, Violation, Workspace @@ -15,7 +13,6 @@ import fs from "node:fs"; import path from "node:path"; import {DEFAULT_CPD_ENGINE_CONFIG} from "../src/config"; import {Language} from "../src/constants"; -import os from "node:os"; changeWorkingDirectoryToPackageRoot(); @@ -362,18 +359,4 @@ describe('Tests for the getEngineVersion method of CpdEngine', () => { expect(version).toMatch(/\d+\.\d+\.\d+.*/); }); -}); - -function createDescribeOptions(workspace?: Workspace): DescribeOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} - -function createRunOptions(workspace: Workspace): RunOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} +}); \ No newline at end of file diff --git a/packages/code-analyzer-pmd-engine/test/pmd-engine.test.ts b/packages/code-analyzer-pmd-engine/test/pmd-engine.test.ts index 798719eb..fa755922 100644 --- a/packages/code-analyzer-pmd-engine/test/pmd-engine.test.ts +++ b/packages/code-analyzer-pmd-engine/test/pmd-engine.test.ts @@ -1,13 +1,13 @@ -import {changeWorkingDirectoryToPackageRoot} from "./test-helpers"; +import {changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions} from "./test-helpers"; import { ConfigObject, - ConfigValueExtractor, DescribeOptions, + ConfigValueExtractor, DescribeRulesProgressEvent, EngineRunResults, EventType, LogEvent, LogLevel, - RuleDescription, RunOptions, + RuleDescription, RunRulesProgressEvent, SeverityLevel, Violation, @@ -673,20 +673,6 @@ function expectNoDuplicateRuleNames(ruleDescriptions: RuleDescription[]): void { } } -function createDescribeOptions(workspace?: Workspace): DescribeOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} - -function createRunOptions(workspace: Workspace): RunOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} - function sortRulesByName(ruleDescriptions: RuleDescription[]): RuleDescription[] { return ruleDescriptions.sort((a, b) => a.name.localeCompare(b.name)); } diff --git a/packages/code-analyzer-pmd-engine/test/pmd-rule-mappings.test.ts b/packages/code-analyzer-pmd-engine/test/pmd-rule-mappings.test.ts index 7429e01d..9a0e0fe4 100644 --- a/packages/code-analyzer-pmd-engine/test/pmd-rule-mappings.test.ts +++ b/packages/code-analyzer-pmd-engine/test/pmd-rule-mappings.test.ts @@ -2,12 +2,12 @@ import {PmdEngine} from "../src/pmd-engine"; import {DEFAULT_PMD_ENGINE_CONFIG} from "../src/config"; import {RuleDescription} from "@salesforce/code-analyzer-engine-api"; import {RULE_MAPPINGS} from "../src/pmd-rule-mappings"; -import os from "node:os"; +import { createDescribeOptions } from "./test-helpers"; describe('Tests for the rule-mappings', () => { it('Test that the list of all bundled PMD rules (from all languages) matches the list of languages in our RULE_MAPPINGS list', async () => { const engine: PmdEngine = new PmdEngine(DEFAULT_PMD_ENGINE_CONFIG); - const ruleDescriptions: RuleDescription[] = await engine.describeRules({logFolder: os.tmpdir()}); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions()); const actualRuleNames: Set = new Set(ruleDescriptions.map(rd => rd.name)); const ruleNamesInRuleMappings: Set = new Set(Object.keys(RULE_MAPPINGS)); diff --git a/packages/code-analyzer-pmd-engine/test/test-helpers.ts b/packages/code-analyzer-pmd-engine/test/test-helpers.ts index 4d98690e..6b9bd681 100644 --- a/packages/code-analyzer-pmd-engine/test/test-helpers.ts +++ b/packages/code-analyzer-pmd-engine/test/test-helpers.ts @@ -1,5 +1,8 @@ -import process from "node:process"; -import path from "node:path"; +import { DescribeOptions, RunOptions, Workspace } from "@salesforce/code-analyzer-engine-api"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as process from "node:process"; export function changeWorkingDirectoryToPackageRoot() { let original_working_directory: string; @@ -16,3 +19,19 @@ export function changeWorkingDirectoryToPackageRoot() { process.chdir(original_working_directory); }); } + +export function createDescribeOptions(workspace?: Workspace): DescribeOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} + +export function createRunOptions(workspace: Workspace): RunOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} diff --git a/packages/code-analyzer-pmd-engine/tsconfig.json b/packages/code-analyzer-pmd-engine/tsconfig.json index 8f5b9142..111c54ac 100644 --- a/packages/code-analyzer-pmd-engine/tsconfig.json +++ b/packages/code-analyzer-pmd-engine/tsconfig.json @@ -1,10 +1,8 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "composite": true, - "outDir": "./dist", "rootDir": ".", - "isolatedModules": true + "noEmit": true }, "include": [ "./src", diff --git a/packages/code-analyzer-regex-engine/package.json b/packages/code-analyzer-regex-engine/package.json index 4cd86dd1..64f4c56d 100644 --- a/packages/code-analyzer-regex-engine/package.json +++ b/packages/code-analyzer-regex-engine/package.json @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.4", "p-limit": "^3.1.0" @@ -38,7 +38,7 @@ ], "scripts": { "build": "tsc --build tsconfig.build.json --verbose", - "test": "jest --coverage", + "test": "tsc --build tsconfig.json && jest --coverage", "lint": "eslint src/**/*.ts", "package": "npm pack", "all": "npm run build && npm run lint && npm run test && npm run package", diff --git a/packages/code-analyzer-regex-engine/test/end-to-end.test.ts b/packages/code-analyzer-regex-engine/test/end-to-end.test.ts index 12d30395..c682cb63 100644 --- a/packages/code-analyzer-regex-engine/test/end-to-end.test.ts +++ b/packages/code-analyzer-regex-engine/test/end-to-end.test.ts @@ -10,8 +10,7 @@ import { Workspace } from "@salesforce/code-analyzer-engine-api"; import path from "node:path"; -import {changeWorkingDirectoryToPackageRoot} from "./test-helpers"; -import os from "node:os"; +import {changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions} from "./test-helpers"; changeWorkingDirectoryToPackageRoot(); @@ -31,9 +30,9 @@ describe('End to end test', () => { const resolvedConfig: ConfigObject = await plugin.createEngineConfig(availableEngineNames[0], new ConfigValueExtractor(customConfig, 'engines.regex')); const engine: Engine = await plugin.createEngine(availableEngineNames[0], resolvedConfig); const workspace: Workspace = new Workspace('id', [path.resolve(__dirname, 'test-data', 'workspaceWithPythonFile')]); - const ruleDescriptions: RuleDescription[] = await engine.describeRules({logFolder: os.tmpdir(), workspace: workspace}); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions(workspace)); const recommendedRuleNames: string[] = ruleDescriptions.filter(rd => rd.tags.includes('Recommended')).map(rd => rd.name); - const engineRunResults: EngineRunResults = await engine.runRules(recommendedRuleNames, {logFolder: os.tmpdir(), workspace: workspace}); + const engineRunResults: EngineRunResults = await engine.runRules(recommendedRuleNames, createRunOptions(workspace)); const violationsFromPyFile: Violation[] = engineRunResults.violations.filter(v => path.extname(v.codeLocations[0].file) === '.py'); expect(violationsFromPyFile).toHaveLength(1); @@ -44,4 +43,4 @@ describe('End to end test', () => { expect(violationsFromClsFile).toHaveLength(1); expect(violationsFromClsFile.map(v => v.ruleName)).toEqual(['NoTrailingWhitespace']); }); -}); +}); \ No newline at end of file diff --git a/packages/code-analyzer-regex-engine/test/engine.test.ts b/packages/code-analyzer-regex-engine/test/engine.test.ts index f559a75a..b72026d4 100644 --- a/packages/code-analyzer-regex-engine/test/engine.test.ts +++ b/packages/code-analyzer-regex-engine/test/engine.test.ts @@ -1,8 +1,7 @@ import {RegexEngine} from "../src/engine"; import path from "node:path"; -import {changeWorkingDirectoryToPackageRoot} from "./test-helpers"; +import {changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions} from "./test-helpers"; import { - DescribeOptions, EngineRunResults, RuleDescription, RunOptions, @@ -16,7 +15,6 @@ import { DEFAULT_SEVERITY_LEVEL } from "../src/config"; import {createBaseRegexRules, RULE_RESOURCE_URLS, TERMS_WITH_IMPLICIT_BIAS} from "../src/plugin"; -import os from "node:os"; changeWorkingDirectoryToPackageRoot(); @@ -849,18 +847,4 @@ describe('Tests for getEngineVersion', () => { expect(version).toMatch(/\d+\.\d+\.\d+.*/); }); -}); - -function createDescribeOptions(workspace?: Workspace): DescribeOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} - -function createRunOptions(workspace: Workspace): RunOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} +}); \ No newline at end of file diff --git a/packages/code-analyzer-regex-engine/test/test-helpers.ts b/packages/code-analyzer-regex-engine/test/test-helpers.ts index 4d98690e..6b9bd681 100644 --- a/packages/code-analyzer-regex-engine/test/test-helpers.ts +++ b/packages/code-analyzer-regex-engine/test/test-helpers.ts @@ -1,5 +1,8 @@ -import process from "node:process"; -import path from "node:path"; +import { DescribeOptions, RunOptions, Workspace } from "@salesforce/code-analyzer-engine-api"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as process from "node:process"; export function changeWorkingDirectoryToPackageRoot() { let original_working_directory: string; @@ -16,3 +19,19 @@ export function changeWorkingDirectoryToPackageRoot() { process.chdir(original_working_directory); }); } + +export function createDescribeOptions(workspace?: Workspace): DescribeOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} + +export function createRunOptions(workspace: Workspace): RunOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} diff --git a/packages/code-analyzer-regex-engine/tsconfig.json b/packages/code-analyzer-regex-engine/tsconfig.json index 8f5b9142..111c54ac 100644 --- a/packages/code-analyzer-regex-engine/tsconfig.json +++ b/packages/code-analyzer-regex-engine/tsconfig.json @@ -1,10 +1,8 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "composite": true, - "outDir": "./dist", "rootDir": ".", - "isolatedModules": true + "noEmit": true }, "include": [ "./src", diff --git a/packages/code-analyzer-retirejs-engine/package.json b/packages/code-analyzer-retirejs-engine/package.json index e2c801e1..3ad5cb84 100644 --- a/packages/code-analyzer-retirejs-engine/package.json +++ b/packages/code-analyzer-retirejs-engine/package.json @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.4", "node-stream-zip": "^1.15.0", @@ -41,7 +41,7 @@ "scripts": { "prebuild": "node build-tools/updateRetireJsVulns.mjs", "build": "tsc --build tsconfig.build.json --verbose", - "test": "jest --coverage", + "test": "tsc --build tsconfig.json && jest --coverage", "lint": "eslint src/**/*.ts", "package": "npm pack", "all": "npm run build && npm run lint && npm run test && npm run package", diff --git a/packages/code-analyzer-retirejs-engine/src/engine.ts b/packages/code-analyzer-retirejs-engine/src/engine.ts index c4a6b411..6b94381b 100644 --- a/packages/code-analyzer-retirejs-engine/src/engine.ts +++ b/packages/code-analyzer-retirejs-engine/src/engine.ts @@ -74,7 +74,7 @@ export class RetireJsEngine extends Engine { async runRules(ruleNames: string[], runOptions: RunOptions): Promise { const targetFiles: string[] = await this.getRelevantFiles(runOptions.workspace); - const findings: Finding[] = await this.retireJsExecutor.execute(targetFiles); + const findings: Finding[] = await this.retireJsExecutor.execute(targetFiles, runOptions.workingFolder); return { violations: toViolations(findings).filter(v => ruleNames.includes(v.ruleName)) }; diff --git a/packages/code-analyzer-retirejs-engine/src/executor.ts b/packages/code-analyzer-retirejs-engine/src/executor.ts index 3954b4a8..79da404a 100644 --- a/packages/code-analyzer-retirejs-engine/src/executor.ts +++ b/packages/code-analyzer-retirejs-engine/src/executor.ts @@ -6,7 +6,6 @@ import path from "node:path"; import {DecoratedStreamZip} from './zip-decorator'; import {getMessage} from "./messages"; import {LogLevel} from "@salesforce/code-analyzer-engine-api"; -import {createTempDir} from "@salesforce/code-analyzer-engine-api/utils"; // To handle the special case where a vulnerable library is found within a zip archive, a RetireJsExecutor can use this // marker to update the file field to look like ::[ZIPPED_FILE]:: which the engine handles. @@ -17,7 +16,7 @@ const RETIRE_COMMAND: string = utils.findCommand('retire'); export const JS_EXTENSIONS = ['.js', '.mjs', '.cjs']; export interface RetireJsExecutor { - execute(filesAndFolders: string[]): Promise + execute(filesAndFolders: string[], workingFolder: string): Promise } export type EmitLogEventFcn = (logLevel: LogLevel, msg: string) => void; @@ -40,17 +39,20 @@ const IS_WINDOWS: boolean = process.platform.startsWith('win'); export class SimpleRetireJsExecutor implements RetireJsExecutor { private readonly emitLogEvent: EmitLogEventFcn; + // A counter to just help generate unique numbers to be appended to the generated result files. + private uniqNameCounter: number = 0; + constructor(emitLogEvent: EmitLogEventFcn = NO_OP) { this.emitLogEvent = emitLogEvent; } - async execute(targetFilesAndFolders: string[]): Promise { + async execute(targetFilesAndFolders: string[], workingFolder: string): Promise { let findings: Finding[] = []; for (const fileOrFolder of targetFilesAndFolders) { if (fs.statSync(fileOrFolder).isFile()) { findings = findings.concat(await this.scanFile(fileOrFolder)); } else { - findings = findings.concat(await this.scanFolder(fileOrFolder)); + findings = findings.concat(await this.scanFolder(fileOrFolder, workingFolder)); } } return findings; @@ -62,10 +64,10 @@ export class SimpleRetireJsExecutor implements RetireJsExecutor { throw new Error('Currently the SimpleRetireJsExecutor does not support scanning individual files.'); } - private async scanFolder(folder: string): Promise { - const tempOutputFile: string = (await createTempDir()) + path.sep + 'output.json'; + private async scanFolder(folderToScan: string, workingFolder: string): Promise { + const tempOutputFile: string = path.join(workingFolder, `output_${this.uniqNameCounter++}.json`); const commandArgs: string[] = [ - '--path', folder, + '--path', folderToScan, '--exitwith', '13', '--outputformat', 'jsonsimple', '--outputpath', tempOutputFile, @@ -138,9 +140,6 @@ export class AdvancedRetireJsExecutor implements RetireJsExecutor { private readonly simpleExecutor: RetireJsExecutor; private readonly emitLogEvent: EmitLogEventFcn; - // Will contain the parent temporary directory where we place all files to be scanned - private parentTempDir: string = ''; - // Map to associate each temp file (under the parentTempDir) to its original file private readonly tempToOrigFileMap: Map = new Map(); @@ -159,21 +158,21 @@ export class AdvancedRetireJsExecutor implements RetireJsExecutor { /** * Note that this execute function assumes that only files are passed in. */ - async execute(targetFiles: string[]): Promise { + async execute(targetFiles: string[], workingFolder: string): Promise { const { textFiles, zipFiles } = separateTextAndZipFiles(targetFiles); if (textFiles.length + zipFiles.length === 0) { return []; // Quick return } - await this.prepareTempDirs(textFiles); - this.emitLogEvent(LogLevel.Fine, `Created a temporary directory where relevant files will be copied to for scanning: ${this.parentTempDir}`); + const parentFolder: string = await this.prepareTempDirs(textFiles, workingFolder); + this.emitLogEvent(LogLevel.Fine, `Created a temporary directory where relevant files will be copied to for scanning: ${parentFolder}`); await Promise.all([ ...textFiles.map(file => this.processTextFile(file)), - ...zipFiles.map(file => this.processZipFile(file))]); - this.emitLogEvent(LogLevel.Fine, `Finished copying relevant files to temporary directory: '${this.parentTempDir}'`); + ...zipFiles.map(file => this.processZipFile(file, parentFolder))]); + this.emitLogEvent(LogLevel.Fine, `Finished copying relevant files to temporary directory: '${parentFolder}'`); - const findings: Finding[] = await this.simpleExecutor.execute([this.parentTempDir]); + const findings: Finding[] = await this.simpleExecutor.execute([parentFolder], workingFolder); for (let i = 0; i < findings.length; i++) { findings[i].file = this.tempToOrigFileMap.get(findings[i].file) as string; } @@ -184,21 +183,23 @@ export class AdvancedRetireJsExecutor implements RetireJsExecutor { * Create parent temporary directory (that cleans up after itself when process exits) and add subdirectories under * the parent for each of the unique folders containing text files */ - private async prepareTempDirs(textFiles: string[]): Promise { + private async prepareTempDirs(textFiles: string[], workingFolder: string): Promise { + const parentFolder = this.makeUniqueTempDirName(workingFolder); + await fs.promises.mkdir(parentFolder); this.origToTempDirMap.clear(); this.tempToOrigFileMap.clear(); this.uniqNameCounter = 0; - this.parentTempDir = await createTempDir(); const mkdirPromises: Promise[] = []; for (const textFile of textFiles) { const folder: string = path.dirname(textFile); if (!this.origToTempDirMap.has(folder)) { - const tempDir: string = this.makeUniqueTempDirName(); + const tempDir: string = this.makeUniqueTempDirName(parentFolder); this.origToTempDirMap.set(folder, tempDir); mkdirPromises.push(fs.promises.mkdir(tempDir)); } } - return Promise.all(mkdirPromises); + await Promise.all(mkdirPromises); + return parentFolder; } /** @@ -219,7 +220,7 @@ export class AdvancedRetireJsExecutor implements RetireJsExecutor { * Additionally, we update the tempToOrigFileMap so that the temp file points to the embedded zip file as: * ::[ZIPPED_FILE]:: */ - private async processZipFile(zipFile: string): Promise { + private async processZipFile(zipFile: string, parentFolder: string): Promise { const zip: DecoratedStreamZip = new DecoratedStreamZip({file: zipFile, storeEntries: true}); const entries = await zip.entries(); for (const entry of Object.values(entries)) { @@ -229,7 +230,7 @@ export class AdvancedRetireJsExecutor implements RetireJsExecutor { const zippedFileInfo: path.ParsedPath = path.parse(entry.name); const folderInZip: string = `${zipFile}${ZIPPED_FILE_MARKER}${zippedFileInfo.dir}`; if (!this.origToTempDirMap.has(folderInZip)) { - const tempSubDir: string = this.makeUniqueTempDirName(); + const tempSubDir: string = this.makeUniqueTempDirName(parentFolder); this.origToTempDirMap.set(folderInZip, tempSubDir); await fs.promises.mkdir(tempSubDir); } @@ -250,8 +251,8 @@ export class AdvancedRetireJsExecutor implements RetireJsExecutor { return JS_EXTENSIONS.includes(fileInfo.ext) ? fileInfo.base : `TMPFILE_${this.uniqNameCounter++}.js`; } - private makeUniqueTempDirName(): string { - return `${this.parentTempDir}${path.sep}TMPDIR_${this.uniqNameCounter++}`; + private makeUniqueTempDirName(parentFolder: string): string { + return `${parentFolder}${path.sep}TMPDIR_${this.uniqNameCounter++}`; } } diff --git a/packages/code-analyzer-retirejs-engine/test/end-to-end.test.ts b/packages/code-analyzer-retirejs-engine/test/end-to-end.test.ts index a77aa3d4..671855a2 100644 --- a/packages/code-analyzer-retirejs-engine/test/end-to-end.test.ts +++ b/packages/code-analyzer-retirejs-engine/test/end-to-end.test.ts @@ -7,8 +7,7 @@ import { Workspace } from "@salesforce/code-analyzer-engine-api"; import path from "node:path"; -import {changeWorkingDirectoryToPackageRoot} from "./test-helpers"; -import os from "node:os"; +import {changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions} from "./test-helpers"; changeWorkingDirectoryToPackageRoot(); @@ -27,11 +26,11 @@ describe('End to end test', () => { path.resolve('test', 'test-data', 'scenarios', '1_hasJsLibraryWithVulnerability'), // Expect 3 violations: 1 file with 3 vulnerabilities path.resolve('test', 'test-data', 'scenarios', '6_hasVulnerableResourceAndZipFiles', 'ZipFileAsResource.resource'), // Expect 6 violations: 2 files each with 3 vulnerabilities ]); - const ruleDescriptions: RuleDescription[] = await engine.describeRules({logFolder: os.tmpdir(), workspace: workspace}); + const ruleDescriptions: RuleDescription[] = await engine.describeRules(createDescribeOptions(workspace)); expect(ruleDescriptions).toHaveLength(4); const ruleNames: string[] = ruleDescriptions.map(rd => rd.name); - const engineRunResults: EngineRunResults = await engine.runRules(ruleNames, {logFolder: os.tmpdir(), workspace: workspace}); + const engineRunResults: EngineRunResults = await engine.runRules(ruleNames, createRunOptions(workspace)); expect(engineRunResults.violations).toHaveLength(9); // The details of these violations are already tested in the unit test files so no need to go crazy here. }); -}); +}); \ No newline at end of file diff --git a/packages/code-analyzer-retirejs-engine/test/engine.test.ts b/packages/code-analyzer-retirejs-engine/test/engine.test.ts index fc690f76..70ba2f02 100644 --- a/packages/code-analyzer-retirejs-engine/test/engine.test.ts +++ b/packages/code-analyzer-retirejs-engine/test/engine.test.ts @@ -11,12 +11,11 @@ import { } from "@salesforce/code-analyzer-engine-api"; import {RetireJsEngine} from "../src/engine"; import {RetireJsExecutor} from "../src/executor"; -import {changeWorkingDirectoryToPackageRoot} from "./test-helpers"; -import path from "node:path"; -import fs from "node:fs"; +import {changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions} from "./test-helpers"; +import * as path from "node:path"; +import * as fs from "node:fs"; import {Finding} from "retire/lib/types"; import {getMessage} from "../src/messages"; -import os from "node:os"; changeWorkingDirectoryToPackageRoot(); @@ -222,19 +221,4 @@ class StubRetireJsExecutor implements RetireJsExecutor { const jsonStr: string = fs.readFileSync(path.resolve('test','test-data','sampleRetireJsExecutorFindings.json'),'utf-8'); return JSON.parse(jsonStr) as Finding[]; } -} - - -function createDescribeOptions(workspace?: Workspace): DescribeOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} - -function createRunOptions(workspace: Workspace): RunOptions { - return { - logFolder: os.tmpdir(), - workspace: workspace - } -} +} \ No newline at end of file diff --git a/packages/code-analyzer-retirejs-engine/test/executor.test.ts b/packages/code-analyzer-retirejs-engine/test/executor.test.ts index 71dbe912..e30e0a4d 100644 --- a/packages/code-analyzer-retirejs-engine/test/executor.test.ts +++ b/packages/code-analyzer-retirejs-engine/test/executor.test.ts @@ -2,27 +2,31 @@ import {changeWorkingDirectoryToPackageRoot} from "./test-helpers"; import {AdvancedRetireJsExecutor, RetireJsExecutor, SimpleRetireJsExecutor, ZIPPED_FILE_MARKER} from "../src/executor"; import {Component, Finding} from "retire/lib/types"; import path from "node:path"; +import * as fs from "node:fs"; +import * as os from "node:os"; import {Workspace} from "@salesforce/code-analyzer-engine-api"; -import {createTempDir} from '@salesforce/code-analyzer-engine-api/utils'; changeWorkingDirectoryToPackageRoot(); describe('Tests for the AdvancedRetireJsExecutor', () => { let executor: RetireJsExecutor; - beforeAll(() => { + let workingFolder: string; + + beforeEach(async () => { executor = new AdvancedRetireJsExecutor(); + workingFolder = await createTempDir() }); it('When running a directory containing no violations, then output is an empty array.', async () => { const workspace: Workspace = new Workspace('id', [await createTempDir()]); - const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles()); + const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles(), workingFolder); expect(findings).toEqual([]); }); it('When vulnerable js library exists in folder, then AdvancedRetireJsExecutor reports it', async () => { const workspace: Workspace = new Workspace('id', [ path.resolve('test','test-data','scenarios','1_hasJsLibraryWithVulnerability')]); - const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles()); + const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles(), workingFolder); const expectedFindings: Finding[] = [{ file: path.resolve('test','test-data','scenarios','1_hasJsLibraryWithVulnerability','jquery-3.1.0.js'), results: getExpectedJQueryResults("filename") @@ -33,28 +37,28 @@ describe('Tests for the AdvancedRetireJsExecutor', () => { it('When no vulnerable js libraries exists in folder, then AdvancedRetireJsExecutor reports empty findings', async () => { const workspace: Workspace = new Workspace('id', [ path.resolve('test','test-data','scenarios','2_hasJsLibraryWithoutVulnerability')]); - const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles()); + const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles(), workingFolder); expect(findings).toEqual([]); }); it('When no js libraries exists in folder, then AdvancedRetireJsExecutor reports empty findings', async () => { const workspace: Workspace = new Workspace('id', [ path.resolve('test','test-data','scenarios','3_hasNoJsLibraries')]); - const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles()); + const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles(), workingFolder); expect(findings).toEqual([]); }); it('When resource files without vulnerabilities in folder, then AdvancedRetireJsExecutor reports empty findings', async () => { const workspace: Workspace = new Workspace('id', [ path.resolve('test','test-data','scenarios','4_hasResourceFilesWithoutVulnerabilities')]) - const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles()); + const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles(), workingFolder); expect(findings).toEqual([]); }); it('When folder contains vulnerabilities in files with odd extension or no extension, then AdvancedRetireJsExecutor finds them', async () => { const workspace: Workspace = new Workspace('id', [ path.resolve('test','test-data','scenarios','5_hasVulnerabilitiesInFilesWithOddExtOrNoExt')]); - const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles()); + const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles(), workingFolder); expect(findings).toHaveLength(2); expect(findings).toContainEqual({ file: path.resolve('test','test-data','scenarios','5_hasVulnerabilitiesInFilesWithOddExtOrNoExt','JsResWithOddExt.foo'), @@ -69,7 +73,7 @@ describe('Tests for the AdvancedRetireJsExecutor', () => { it('When folder contains vulnerabilities within zip files, then AdvancedRetireJsExecutor finds them', async () => { const workspace: Workspace = new Workspace('id', [ path.resolve('test','test-data','scenarios','6_hasVulnerableResourceAndZipFiles')]); - const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles()); + const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles(), workingFolder); expect(findings).toHaveLength(8); for (const zipFileName of ['ZipFile.zip', 'ZipFileAsResource.resource', 'ZipFileWithOddExt.foo', 'ZipFileWithNoExt']) { expect(findings).toContainEqual({ @@ -86,7 +90,7 @@ describe('Tests for the AdvancedRetireJsExecutor', () => { it('When folder contains vulnerabilities in files with odd extension or no extension, then AdvancedRetireJsExecutor finds them', async () => { const workspace: Workspace = new Workspace('id', [ path.resolve('test','test-data','scenarios','7_hasZipFolderWithVulnFileInChildFolders')]); - const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles()); + const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles(), workingFolder); expect(findings).toHaveLength(2); expect(findings).toContainEqual({ file: path.resolve('test', 'test-data', 'scenarios', '7_hasZipFolderWithVulnFileInChildFolders', 'ZipWithDirectories.zip') + ZIPPED_FILE_MARKER + 'FilledParentFolder/ChildFolderWithText/JsFileWithoutExt', @@ -105,7 +109,7 @@ describe('Tests for the AdvancedRetireJsExecutor', () => { path.resolve('test','test-data','scenarios','6_hasVulnerableResourceAndZipFiles', 'ZipFileWithNoExt'), // contains 2 findings ... path.resolve('test','test-data','scenarios','6_hasVulnerableResourceAndZipFiles', 'ZipFileWithNoExt.resource-meta.xml') // ... since it also has metadata file with it ]); - const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles()); + const findings: Finding[] = await executor.execute(await workspace.getTargetedFiles(), workingFolder); // Note that the other files inside of the 6_hasVulnerableResourceAndZipFiles folder shouldn't be scanned. Thus, the total should be only be 3. expect(findings).toHaveLength(3); @@ -126,24 +130,30 @@ describe('Tests for the AdvancedRetireJsExecutor', () => { describe('Tests for the SimpleRetireJsExecutor', () => { let executor: RetireJsExecutor; - beforeAll(() => { + let workingFolder: string; + + beforeEach(async () => { executor = new SimpleRetireJsExecutor(); + workingFolder = await createTempDir() }); it('When file is specified among input paths, then SimpleRetireJsExecutor currently errors out since it does not support files', async () => { - await expect(executor.execute([path.resolve('test','test-data','scenarios','1_hasJsLibraryWithVulnerability', 'jquery-3.1.0.js')])) - .rejects.toThrow('Currently the SimpleRetireJsExecutor does not support scanning individual files.'); + await expect(executor.execute( + [path.resolve('test','test-data','scenarios','1_hasJsLibraryWithVulnerability', 'jquery-3.1.0.js')], + workingFolder + )).rejects.toThrow('Currently the SimpleRetireJsExecutor does not support scanning individual files.'); }); describe.skip('These tests are just nice to have right now since we do not expose SimpleRetireJsExecutor. So they are skipped.', () => { it('When running a directory containing no violations, then output is an empty array.', async () => { - const findings: Finding[] = await executor.execute([await createTempDir()]); + const findings: Finding[] = await executor.execute([await createTempDir()], workingFolder); expect(findings).toEqual([]); }); it('When vulnerable js library exists in folder, then SimpleRetireJsExecutor reports it', async () => { const findings: Finding[] = await executor.execute([ - path.resolve('test','test-data','scenarios','1_hasJsLibraryWithVulnerability')]); + path.resolve('test','test-data','scenarios','1_hasJsLibraryWithVulnerability')], + workingFolder); const expectedFindings: Finding[] = [{ file: path.resolve('test','test-data','scenarios','1_hasJsLibraryWithVulnerability','jquery-3.1.0.js'), results: getExpectedJQueryResults("filename") @@ -154,37 +164,43 @@ describe('Tests for the SimpleRetireJsExecutor', () => { it('When no vulnerable js libraries exists in folder, then SimpleRetireJsExecutor reports empty findings', async () => { const findings: Finding[] = await executor.execute([ - path.resolve('test','test-data','scenarios','2_hasJsLibraryWithoutVulnerability')]); + path.resolve('test','test-data','scenarios','2_hasJsLibraryWithoutVulnerability')], + workingFolder); expect(findings).toEqual([]); }); it('When no js libraries exists in folder, then SimpleRetireJsExecutor reports empty findings', async () => { const findings: Finding[] = await executor.execute([ - path.resolve('test','test-data','scenarios','3_hasNoJsLibraries')]); + path.resolve('test','test-data','scenarios','3_hasNoJsLibraries')], + workingFolder); expect(findings).toEqual([]); }); it('When resource files without vulnerabilities in folder, then SimpleRetireJsExecutor reports empty findings', async () => { const findings: Finding[] = await executor.execute([ - path.resolve('test','test-data','scenarios','4_hasResourceFilesWithoutVulnerabilities')]); + path.resolve('test','test-data','scenarios','4_hasResourceFilesWithoutVulnerabilities')], + workingFolder); expect(findings).toEqual([]); }); it('When folder contains vulnerabilities in files with odd extension or no extension, then SimpleRetireJsExecutor does not find them', async () => { const findings: Finding[] = await executor.execute([ - path.resolve('test','test-data','scenarios','5_hasVulnerabilitiesInFilesWithOddExtOrNoExt')]); + path.resolve('test','test-data','scenarios','5_hasVulnerabilitiesInFilesWithOddExtOrNoExt')], + workingFolder); expect(findings).toEqual([]); }); it('When folder contains vulnerabilities within resource and zip files, then SimpleRetireJsExecutor does not find them', async () => { const findings: Finding[] = await executor.execute([ - path.resolve('test','test-data','scenarios','6_hasVulnerableResourceAndZipFiles')]); + path.resolve('test','test-data','scenarios','6_hasVulnerableResourceAndZipFiles')], + workingFolder); expect(findings).toEqual([]); }); it('When folder contains vulnerabilities in files with odd extension or no extension, then SimpleRetireJsExecutor does not find them', async () => { const findings: Finding[] = await executor.execute([ - path.resolve('test','test-data','scenarios','7_hasZipFolderWithVulnFileInChildFolders')]); + path.resolve('test','test-data','scenarios','7_hasZipFolderWithVulnFileInChildFolders')], + workingFolder); expect(findings).toEqual([]); }); }); @@ -260,5 +276,9 @@ function getExpectedJQueryResults(detection: string): Component[] { } ] } - ] as Component[]; - } + ] as Component[]; +} + +function createTempDir(): Promise { + return fs.promises.mkdtemp(path.join(os.tmpdir(), 'tmp-')); +} diff --git a/packages/code-analyzer-retirejs-engine/test/test-helpers.ts b/packages/code-analyzer-retirejs-engine/test/test-helpers.ts index c32fc377..a3524df0 100644 --- a/packages/code-analyzer-retirejs-engine/test/test-helpers.ts +++ b/packages/code-analyzer-retirejs-engine/test/test-helpers.ts @@ -1,5 +1,8 @@ -import process from "node:process"; -import path from "node:path"; +import { DescribeOptions, RunOptions, Workspace } from "@salesforce/code-analyzer-engine-api"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as process from "node:process"; export function changeWorkingDirectoryToPackageRoot() { let original_working_directory: string; @@ -15,4 +18,20 @@ export function changeWorkingDirectoryToPackageRoot() { afterAll(() => { process.chdir(original_working_directory); }); -} \ No newline at end of file +} + +export function createDescribeOptions(workspace?: Workspace): DescribeOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} + +export function createRunOptions(workspace: Workspace): RunOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} diff --git a/packages/code-analyzer-retirejs-engine/tsconfig.json b/packages/code-analyzer-retirejs-engine/tsconfig.json index 126526f5..06626242 100644 --- a/packages/code-analyzer-retirejs-engine/tsconfig.json +++ b/packages/code-analyzer-retirejs-engine/tsconfig.json @@ -1,10 +1,8 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "composite": true, - "outDir": "./dist", "rootDir": ".", - "isolatedModules": true + "noEmit": true }, "include": [ "./src", diff --git a/packages/code-analyzer-sfge-engine/package.json b/packages/code-analyzer-sfge-engine/package.json index f9808f85..5ecc41aa 100644 --- a/packages/code-analyzer-sfge-engine/package.json +++ b/packages/code-analyzer-sfge-engine/package.json @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.28.0", + "@salesforce/code-analyzer-engine-api": "0.29.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -41,7 +41,7 @@ "build-typescript": "tsc --build tsconfig.build.json --verbose", "build": "npm run build-java && npm run build-typescript", "test-java": "sh ./gradlew test jacocoTestReport jacocoTestCoverageVerification", - "test-typescript": "jest --coverage", + "test-typescript": "tsc --build tsconfig.json && jest --coverage", "test": "npm run test-java && npm run test-typescript", "lint": "eslint src/**/*.ts", "package": "npm pack", diff --git a/packages/code-analyzer-sfge-engine/src/engine.ts b/packages/code-analyzer-sfge-engine/src/engine.ts index 51d3b332..ac3d3a04 100644 --- a/packages/code-analyzer-sfge-engine/src/engine.ts +++ b/packages/code-analyzer-sfge-engine/src/engine.ts @@ -98,6 +98,7 @@ export class SfgeEngine extends Engine { await runOptions.workspace.getTargetedFiles(), relevantWorkspaceFiles, sfgeRunOptions, + runOptions.workingFolder, (innerPerc: number, message?: string) => this.emitRunRulesProgressEvent(5 + 93*innerPerc/100, message) // 5%-98% ); @@ -144,7 +145,7 @@ export class SfgeEngine extends Engine { if (workspace && (await this.getRelevantFilesInWorkspace(workspace)).length === 0) { this.sfgeRuleInfoListCache.set(cacheKey, []); } else { - const ruleInfoList: SfgeRuleInfo[] = await this.sfgeWrapper.invokeDescribeCommand(emitProgress, logFolder); + const ruleInfoList: SfgeRuleInfo[] = await this.sfgeWrapper.invokeDescribeCommand(options.workingFolder, emitProgress, logFolder); this.sfgeRuleInfoListCache.set(cacheKey, ruleInfoList); } } diff --git a/packages/code-analyzer-sfge-engine/src/sfge-wrapper.ts b/packages/code-analyzer-sfge-engine/src/sfge-wrapper.ts index 34d30d33..7fe2c1ca 100644 --- a/packages/code-analyzer-sfge-engine/src/sfge-wrapper.ts +++ b/packages/code-analyzer-sfge-engine/src/sfge-wrapper.ts @@ -6,7 +6,7 @@ import { SHARED_MESSAGE_CATALOG, TelemetryData } from '@salesforce/code-analyzer-engine-api'; -import {createTempDir, JavaCommandExecutor} from '@salesforce/code-analyzer-engine-api/utils'; +import {JavaCommandExecutor} from '@salesforce/code-analyzer-engine-api/utils'; import {getMessage} from "./messages"; import {Clock, formatToDateTimeString} from './utils'; @@ -90,10 +90,9 @@ export class RuntimeSfgeWrapper { this.emitTelemetryEvent = emitTelemetryEvent; } - public async invokeDescribeCommand(emitProgress: (percComplete: number) => void, logFolder: string): Promise { - const tmpDir: string = await this.getTemporaryWorkingDir(); + public async invokeDescribeCommand(workingFolder: string, emitProgress: (percComplete: number) => void, logFolder: string): Promise { const logFilePath: string = path.join(logFolder, this.logFileName); - const sfgeRulesOutputFile: string = path.join(tmpDir, 'ruleInfo.json'); + const sfgeRulesOutputFile: string = path.join(workingFolder, 'ruleInfo.json'); this.emitLogEvent(LogLevel.Debug, getMessage('LoggingToFile', 'describe', logFilePath)); emitProgress(10); @@ -116,16 +115,16 @@ export class RuntimeSfgeWrapper { } } - public async invokeRunCommand(selectedRuleInfos: SfgeRuleInfo[], targetPaths: string[], projectFilePaths: string[], sfgeRunOptions: SfgeRunOptions, emitProgress: (percComplete: number) => void): Promise { - const tmpDir: string = await this.getTemporaryWorkingDir(); + public async invokeRunCommand(selectedRuleInfos: SfgeRuleInfo[], targetPaths: string[], projectFilePaths: string[], + sfgeRunOptions: SfgeRunOptions, workingFolder: string, emitProgress: (percComplete: number) => void): Promise { emitProgress(2); - const inputFileName: string = path.join(tmpDir, 'sfgeInput.json'); + const inputFileName: string = path.join(workingFolder, 'sfgeInput.json'); const logFilePath: string = path.join(sfgeRunOptions.logFolder, this.logFileName); const ruleNames: string[] = selectedRuleInfos.map(sri => sri.name); await this.createSfgeInputFile(inputFileName, ruleNames, targetPaths, projectFilePaths); - const resultsOutputFile: string = path.join(tmpDir, 'resultsFile.json'); + const resultsOutputFile: string = path.join(workingFolder, 'resultsFile.json'); this.emitLogEvent(LogLevel.Debug, getMessage('LoggingToFile', 'run', logFilePath)); emitProgress(10); @@ -164,13 +163,6 @@ export class RuntimeSfgeWrapper { } } - private async getTemporaryWorkingDir(): Promise { - if (this.temporaryWorkingDir === undefined) { - this.temporaryWorkingDir = await createTempDir(); - } - return this.temporaryWorkingDir; - } - private async createSfgeInputFile(filePath: string, rules: string[], targets: string[], allWorkspaceFiles: string[]): Promise { const sfgeTargets: SfgeTarget[] = targets.map(target => { return { diff --git a/packages/code-analyzer-sfge-engine/test/engine.test.ts b/packages/code-analyzer-sfge-engine/test/engine.test.ts index cd510c7b..d92c9396 100644 --- a/packages/code-analyzer-sfge-engine/test/engine.test.ts +++ b/packages/code-analyzer-sfge-engine/test/engine.test.ts @@ -2,21 +2,19 @@ import os from 'node:os'; import fs from 'node:fs'; import path from 'node:path'; import { - DescribeOptions, DescribeRulesProgressEvent, EngineRunResults, EventType, LogEvent, LogLevel, RuleDescription, - RunOptions, RunRulesProgressEvent, TelemetryEvent, Workspace } from "@salesforce/code-analyzer-engine-api"; import {DEFAULT_SFGE_ENGINE_CONFIG, SfgeEngineConfig} from "../src/config"; import {SfgeEngine} from "../src/engine"; -import {changeWorkingDirectoryToPackageRoot, FixedClock} from "./test-helpers"; +import {changeWorkingDirectoryToPackageRoot, createDescribeOptions, createRunOptions, FixedClock} from "./test-helpers"; changeWorkingDirectoryToPackageRoot(); @@ -398,18 +396,4 @@ function expectProgressEventsToAscend(progressEvents: number[]): void { for (let i = 0; i < progressEvents.length - 1; i++) { expect(progressEvents[i]).toBeLessThan(progressEvents[i + 1]); } -} - -function createDescribeOptions(workspace?: Workspace): DescribeOptions { - return { - logFolder: os.tmpdir(), - workspace - }; -} - -function createRunOptions(workspace: Workspace): RunOptions { - return { - logFolder: os.tmpdir(), - workspace - }; -} +} \ No newline at end of file diff --git a/packages/code-analyzer-sfge-engine/test/test-helpers.ts b/packages/code-analyzer-sfge-engine/test/test-helpers.ts index b97a336b..fcd33054 100644 --- a/packages/code-analyzer-sfge-engine/test/test-helpers.ts +++ b/packages/code-analyzer-sfge-engine/test/test-helpers.ts @@ -1,5 +1,8 @@ -import process from "node:process"; -import path from "node:path"; +import { DescribeOptions, RunOptions, Workspace } from "@salesforce/code-analyzer-engine-api"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as process from "node:process"; import {Clock} from '../src/utils'; export function changeWorkingDirectoryToPackageRoot() { @@ -29,3 +32,19 @@ export class FixedClock implements Clock { return this.fixedTimestamp; } } + +export function createDescribeOptions(workspace?: Workspace): DescribeOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} + +export function createRunOptions(workspace: Workspace): RunOptions { + return { + logFolder: os.tmpdir(), + workspace: workspace, + workingFolder: fs.mkdtempSync(path.join(os.tmpdir(),'tmp-')) + } +} diff --git a/packages/code-analyzer-sfge-engine/tsconfig.json b/packages/code-analyzer-sfge-engine/tsconfig.json index 8f5b9142..111c54ac 100644 --- a/packages/code-analyzer-sfge-engine/tsconfig.json +++ b/packages/code-analyzer-sfge-engine/tsconfig.json @@ -1,10 +1,8 @@ { "extends": "./tsconfig.build.json", "compilerOptions": { - "composite": true, - "outDir": "./dist", "rootDir": ".", - "isolatedModules": true + "noEmit": true }, "include": [ "./src", diff --git a/tsconfig.base.json b/tsconfig.base.json index d6fdd5a2..ddb9e6e4 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -2,6 +2,7 @@ "compilerOptions": { "declaration": true, "esModuleInterop": true, + "isolatedModules": true, "lib": [ "ES2022" ],