diff --git a/package-lock.json b/package-lock.json index 6bea7717..d1e60ff0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7958,9 +7958,9 @@ "license": "MIT" }, "node_modules/tmp": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", - "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "license": "MIT", "engines": { "node": ">=14.14" @@ -8773,10 +8773,10 @@ }, "packages/code-analyzer-core": { "name": "@salesforce/code-analyzer-core", - "version": "0.33.0", + "version": "0.34.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "csv-stringify": "^6.6.0", "js-yaml": "^4.1.0", @@ -9033,11 +9033,11 @@ }, "packages/code-analyzer-engine-api": { "name": "@salesforce/code-analyzer-engine-api", - "version": "0.27.0", + "version": "0.28.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { "@types/node": "^20.0.0", - "tmp": "^0.2.3" + "tmp": "^0.2.5" }, "devDependencies": { "@eslint/js": "^9.32.0", @@ -9286,15 +9286,15 @@ }, "packages/code-analyzer-eslint-engine": { "name": "@salesforce/code-analyzer-eslint-engine", - "version": "0.30.0", + "version": "0.31.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { "@eslint/js": "^9.32.0", "@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.27.0", - "@salesforce/code-analyzer-eslint8-engine": "0.5.0", + "@salesforce/code-analyzer-engine-api": "0.28.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", "@types/node": "^20.0.0", @@ -9833,7 +9833,7 @@ }, "packages/code-analyzer-eslint8-engine": { "name": "@salesforce/code-analyzer-eslint8-engine", - "version": "0.5.0", + "version": "0.6.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { "@babel/core": "7.27.4", @@ -9841,7 +9841,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.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@salesforce/eslint-config-lwc": "3.7.2", "@salesforce/eslint-plugin-lightning": "1.0.1", "@types/node": "^20.0.0", @@ -10233,10 +10233,10 @@ }, "packages/code-analyzer-flow-engine": { "name": "@salesforce/code-analyzer-flow-engine", - "version": "0.25.0", + "version": "0.26.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -10487,10 +10487,10 @@ }, "packages/code-analyzer-pmd-engine": { "name": "@salesforce/code-analyzer-pmd-engine", - "version": "0.29.0", + "version": "0.30.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -10741,10 +10741,10 @@ }, "packages/code-analyzer-regex-engine": { "name": "@salesforce/code-analyzer-regex-engine", - "version": "0.26.0", + "version": "0.27.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.4", "p-limit": "^3.1.0" @@ -10995,10 +10995,10 @@ }, "packages/code-analyzer-retirejs-engine": { "name": "@salesforce/code-analyzer-retirejs-engine", - "version": "0.25.0", + "version": "0.26.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.4", "node-stream-zip": "^1.15.0", @@ -11250,10 +11250,10 @@ }, "packages/code-analyzer-sfge-engine": { "name": "@salesforce/code-analyzer-sfge-engine", - "version": "0.11.0", + "version": "0.12.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -11507,7 +11507,7 @@ "version": "0.1.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0" }, "devDependencies": { @@ -11759,7 +11759,7 @@ "version": "0.1.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0" }, "devDependencies": { @@ -12011,7 +12011,7 @@ "version": "0.1.0-SNAPSHOT", "license": "BSD-3-Clause", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0" }, "devDependencies": { diff --git a/packages/ENGINE-TEMPLATE/package.json b/packages/ENGINE-TEMPLATE/package.json index 43206805..12226c1a 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.27.0" + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT" }, "devDependencies": { "@eslint/js": "^9.32.0", @@ -60,4 +60,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/T-E-M-P-L-A-T-E/package.json b/packages/T-E-M-P-L-A-T-E/package.json index e41b1894..8a3dd4ea 100644 --- a/packages/T-E-M-P-L-A-T-E/package.json +++ b/packages/T-E-M-P-L-A-T-E/package.json @@ -14,7 +14,7 @@ "types": "dist/index.d.ts", "dependencies": { "@types/node": "^20.0.0", - "@salesforce/code-analyzer-engine-api": "0.27.0" + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT" }, "devDependencies": { "@eslint/js": "^9.32.0", @@ -60,4 +60,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/code-analyzer-core/package.json b/packages/code-analyzer-core/package.json index f4493704..eb0cf4d8 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.33.0", + "version": "0.34.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "csv-stringify": "^6.6.0", "js-yaml": "^4.1.0", @@ -69,4 +69,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/code-analyzer-core/src/code-analyzer.ts b/packages/code-analyzer-core/src/code-analyzer.ts index 6e435704..a73aa8bc 100644 --- a/packages/code-analyzer-core/src/code-analyzer.ts +++ b/packages/code-analyzer-core/src/code-analyzer.ts @@ -1,4 +1,5 @@ import {pathToFileURL} from "node:url"; +import os from "node:os"; import {RuleImpl, RuleSelection, RuleSelectionImpl} from "./rules" import { EngineRunResults, @@ -24,7 +25,13 @@ import * as engApi from "@salesforce/code-analyzer-engine-api" import {Clock, RealClock} from '@salesforce/code-analyzer-engine-api/utils'; import {EventEmitter} from "node:events"; import {CodeAnalyzerConfig, ConfigDescription, EngineOverrides, FIELDS, RuleOverride} from "./config"; -import {EngineProgressAggregator, RuntimeUniqueIdGenerator, toAbsolutePath, UniqueIdGenerator} from "./utils"; +import { + EngineProgressAggregator, + FileSystemHandler, + RuntimeFileSystemHandler, + RuntimeUniqueIdGenerator, + toAbsolutePath, + UniqueIdGenerator} from "./utils"; import fs from "node:fs"; import path from 'node:path'; @@ -95,6 +102,7 @@ export class CodeAnalyzer { private readonly config: CodeAnalyzerConfig; private clock: Clock = new RealClock(); private uniqueIdGenerator: UniqueIdGenerator = new RuntimeUniqueIdGenerator(); + private fileSystemHandler: FileSystemHandler = new RuntimeFileSystemHandler(); private readonly eventEmitter: EventEmitter = new EventEmitter(); private readonly engines: Map = new Map(); private readonly uninstantiableEnginesMap: Map = new Map(); @@ -102,6 +110,7 @@ export class CodeAnalyzer { private readonly engineConfigDescriptions: Map = new Map(); private readonly rulesCache: Map = new Map(); private readonly engineRuleDiscoveryProgressAggregator: EngineProgressAggregator = new EngineProgressAggregator(); + private workingFolderRoot?: string; constructor(config: CodeAnalyzerConfig, version: string = process.version) { this.validateEnvironment(version); @@ -124,6 +133,9 @@ export class CodeAnalyzer { _setUniqueIdGenerator(uniqueIdGenerator: UniqueIdGenerator) { this.uniqueIdGenerator = uniqueIdGenerator; } + _setFileSystemHandler(fileSystemHandler: FileSystemHandler): void { + this.fileSystemHandler = fileSystemHandler; + } /** * Convenience method to return the same CodeAnalyzerConfig instance that was provided to the constructor @@ -296,15 +308,16 @@ export class CodeAnalyzer { // called a second time before the first call to run hasn't finished. This can occur if someone builds // up a bunch of RunResults promises and then does a Promise.all on them. Otherwise, the progress events may // override each other. - - const engineRunOptions: engApi.RunOptions = extractEngineRunOptions(runOptions, this.config.getLogFolder()); + const tmpDirRoot: string = path.join(await this.getWorkingFolderRoot(), `run-${this.clock.formatToDateTimeString()}`); + await this.fileSystemHandler.createDirectory(tmpDirRoot); + const workspace: engApi.Workspace = toEngApiWorkspace(runOptions.workspace); this.emitLogEvent(LogLevel.Debug, getMessage('RunningWithWorkspace', JSON.stringify({ filesAndFolders: runOptions.workspace.getRawFilesAndFolders(), targets: runOptions.workspace.getRawTargets() }))); const runPromises: Promise[] = ruleSelection.getEngineNames().map( - engineName => this.runEngineAndValidateResults(engineName, ruleSelection, engineRunOptions)); + engineName => this.runEngineAndValidateResults(engineName, ruleSelection, this.config.getLogFolder(), workspace, tmpDirRoot)); const engineRunResultsList: EngineRunResults[] = await Promise.all(runPromises); const runResults: RunResultsImpl = new RunResultsImpl(this.clock); @@ -331,16 +344,27 @@ export class CodeAnalyzer { private async getAllRules(workspace?: Workspace): Promise { const cacheKey: string = workspace ? workspace.getWorkspaceId() : process.cwd(); if (!this.rulesCache.has(cacheKey)) { + // TODO: THIS WILL BE CONFIGURABLE SOON. + const tmpDirRoot: string = path.join(await this.getWorkingFolderRoot(), `describe-${this.clock.formatToDateTimeString()}`); + await this.fileSystemHandler.createDirectory(tmpDirRoot); this.engineRuleDiscoveryProgressAggregator.reset(this.getEngineNames()); const engApiWorkspace: engApi.Workspace | undefined = workspace ? toEngApiWorkspace(workspace) : undefined; + const rulePromises: Promise[] = this.getEngineNames().map(engineName => - this.getAllRulesFor(engineName, {workspace: engApiWorkspace, logFolder: this.config.getLogFolder()})); + this.getAllRulesFor(engineName, engApiWorkspace, tmpDirRoot, this.config.getLogFolder())); this.rulesCache.set(cacheKey, (await Promise.all(rulePromises)).flat()); } return this.rulesCache.get(cacheKey)!; } - private async getAllRulesFor(engineName: string, describeOptions: engApi.DescribeOptions): Promise { + private async getAllRulesFor(engineName: string, workspace: engApi.Workspace | undefined, tmpDirRoot: string, logFolder: string): Promise { + const workingFolder: string = path.join(tmpDirRoot, engineName); + await this.fileSystemHandler.createDirectory(workingFolder); + const describeOptions: engApi.DescribeOptions = { + workspace, + workingFolder, + logFolder + }; this.emitLogEvent(LogLevel.Debug, getMessage('GatheringRulesFromEngine', engineName)); let ruleDescriptions: engApi.RuleDescription[] = []; try { @@ -366,7 +390,14 @@ export class CodeAnalyzer { this.emitEvent({type: EventType.RuleSelectionProgressEvent, timestamp: this.clock.now(), percentComplete: aggregatedPerc}); } - private async runEngineAndValidateResults(engineName: string, ruleSelection: RuleSelection, engineRunOptions: engApi.RunOptions): Promise { + private async runEngineAndValidateResults(engineName: string, ruleSelection: RuleSelection, logFolder: string, workspace: engApi.Workspace, tmpDirRoot: string): Promise { + const workingFolder: string = path.join(tmpDirRoot, engineName); + await this.fileSystemHandler.createDirectory(workingFolder); + const engineRunOptions: engApi.RunOptions = { + logFolder, + workspace, + workingFolder + }; this.emitEvent({ type: EventType.EngineRunProgressEvent, timestamp: this.clock.now(), engineName: engineName, percentComplete: 0 }); @@ -539,6 +570,17 @@ export class CodeAnalyzer { private getEngine(engineName: string): engApi.Engine { return this.engines.get(engineName)!; } + + private async getWorkingFolderRoot(): Promise { + if (!this.workingFolderRoot) { + const workingFolderRoot: string = path.join(os.tmpdir(), 'code-analyzer'); + if (!this.fileSystemHandler.directoryExists(workingFolderRoot)) { + await this.fileSystemHandler.createDirectory(workingFolderRoot); + } + this.workingFolderRoot = workingFolderRoot; + } + return this.workingFolderRoot; + } } /** @@ -602,13 +644,6 @@ function validateRuleDescriptions(ruleDescriptions: engApi.RuleDescription[], en } } -function extractEngineRunOptions(runOptions: RunOptions, logFolder: string): engApi.RunOptions { - return { - logFolder: logFolder, - workspace: toEngApiWorkspace(runOptions.workspace), - }; -} - async function validateFileOrFolder(fileOrFolder: string): Promise { const absFileOrFolder: string = toAbsolutePath(fileOrFolder); try { @@ -692,6 +727,7 @@ function validateViolationCodeLocations(violation: engApi.Violation, engineName: engineName, violation.ruleName, codeLocation.endLine, codeLocation.startLine)); } + // istanbul ignore else if (codeLocation.endColumn !== undefined) { if (!isValidLineOrColumn(codeLocation.endColumn)) { throw new Error(getMessage('EngineReturnedViolationWithCodeLocationWithInvalidLineOrColumn', diff --git a/packages/code-analyzer-core/src/utils.ts b/packages/code-analyzer-core/src/utils.ts index 15f0dcbe..c2f96c7a 100644 --- a/packages/code-analyzer-core/src/utils.ts +++ b/packages/code-analyzer-core/src/utils.ts @@ -1,5 +1,7 @@ import * as path from "node:path"; import * as crypto from "node:crypto"; +import fs from "node:fs"; +import { createNamedTempDir } from "@salesforce/code-analyzer-engine-api/utils"; // THIS FILE CONTAINS UTILITIES WHICH ARE USED INTERNALLY ONLY. // None of the following exported interfaces and functions should be exported from the index file. @@ -9,6 +11,22 @@ export function toAbsolutePath(fileOrFolder: string): string { return path.resolve(fileOrFolder.replace(/[\\/]/g, path.sep)); } +export interface FileSystemHandler { + directoryExists(absolutePath: string): boolean; + createDirectory(absolutePath: string): Promise; +} + +export class RuntimeFileSystemHandler implements FileSystemHandler { + + directoryExists(absolutePath: string): boolean { + return fs.existsSync(absolutePath); + } + + async createDirectory(absolutePath: string): Promise { + await createNamedTempDir(path.basename(absolutePath), path.dirname(absolutePath)); + } +} + export interface UniqueIdGenerator { getLocallyUniqueId(prefix: string): string; diff --git a/packages/code-analyzer-core/test/code-analyzer.test.ts b/packages/code-analyzer-core/test/code-analyzer.test.ts index e22df4e3..10d71493 100644 --- a/packages/code-analyzer-core/test/code-analyzer.test.ts +++ b/packages/code-analyzer-core/test/code-analyzer.test.ts @@ -19,8 +19,9 @@ import { } from "../src"; import * as stubs from "./stubs"; import {getMessage} from "../src/messages"; +import os from "node:os"; import path from "node:path"; -import {changeWorkingDirectoryToPackageRoot, FixedUniqueIdGenerator} from "./test-helpers"; +import {changeWorkingDirectoryToPackageRoot, FakeFileSystemHandler, FixedUniqueIdGenerator} from "./test-helpers"; import * as engApi from "@salesforce/code-analyzer-engine-api" import {FixedClock} from "@salesforce/code-analyzer-engine-api/utils" import {UnexpectedEngineErrorRule} from "../src/rules"; @@ -186,14 +187,19 @@ describe("Tests for the run method of CodeAnalyzer", () => { let stubEngine1: stubs.StubEngine1; let stubEngine2: stubs.StubEngine2; let selection: RuleSelection; + let fixedClock: FixedClock; + let fakeFileSystemHandler: FakeFileSystemHandler; const expectedStubEngine1RuleNames: string[] = ['stub1RuleA', 'stub1RuleB', 'stub1RuleC']; const expectedStubEngine2RuleNames: string[] = ['stub2RuleA', 'stub2RuleC']; beforeEach(async () => { sampleTimestamp = new Date(); codeAnalyzer = new CodeAnalyzer(CodeAnalyzerConfig.withDefaults()); - codeAnalyzer._setClock(new FixedClock(sampleTimestamp)); + fixedClock = new FixedClock(sampleTimestamp); + fakeFileSystemHandler = new FakeFileSystemHandler(); + codeAnalyzer._setClock(fixedClock); codeAnalyzer._setUniqueIdGenerator(new FixedUniqueIdGenerator()); + codeAnalyzer._setFileSystemHandler(fakeFileSystemHandler); sampleRunOptions = {workspace: await codeAnalyzer.createWorkspace([__dirname])}; const stubPlugin: stubs.StubEnginePlugin = new stubs.StubEnginePlugin(); await codeAnalyzer.addEnginePlugin(stubPlugin); @@ -209,21 +215,35 @@ describe("Tests for the run method of CodeAnalyzer", () => { ]), }); - const expectedEngineRunOptions: engApi.RunOptions = { + const workingDirectoriesRoot: string = path.join(os.tmpdir(), `code-analyzer`, `run-${fixedClock.formatToDateTimeString()}`); + + const expectedEngineRunOptionsEngine1: engApi.RunOptions = { + logFolder: codeAnalyzer.getConfig().getLogFolder(), + workingFolder: path.join(workingDirectoriesRoot, 'stubEngine1'), + workspace: new engApi.Workspace("FixedId", [SAMPLE_WORKSPACE_FOLDER], [ + path.join(SAMPLE_WORKSPACE_FOLDER, 'someFile.cls')]) + }; + const expectedEngineRunOptionsEngine2: engApi.RunOptions = { logFolder: codeAnalyzer.getConfig().getLogFolder(), + workingFolder: path.join(workingDirectoriesRoot, 'stubEngine2'), workspace: new engApi.Workspace("FixedId", [SAMPLE_WORKSPACE_FOLDER], [ path.join(SAMPLE_WORKSPACE_FOLDER, 'someFile.cls')]) }; + expect(fakeFileSystemHandler.dirWasCreated(workingDirectoriesRoot)).toEqual(true); expect(stubEngine1.runRulesCallHistory).toHaveLength(1); expect(stubEngine1.runRulesCallHistory[0].ruleNames).toEqual(expectedStubEngine1RuleNames); - expectEquivalentRunOptions(stubEngine1.runRulesCallHistory[0].runOptions, expectedEngineRunOptions); + expectEquivalentRunOptions(stubEngine1.runRulesCallHistory[0].runOptions, expectedEngineRunOptionsEngine1); + expect(fakeFileSystemHandler.dirWasCreated(expectedEngineRunOptionsEngine1.workingFolder)).toEqual(true); expect(stubEngine2.runRulesCallHistory).toHaveLength(1); expect(stubEngine2.runRulesCallHistory[0].ruleNames).toEqual(expectedStubEngine2RuleNames); - expectEquivalentRunOptions(stubEngine2.runRulesCallHistory[0].runOptions, expectedEngineRunOptions); + expectEquivalentRunOptions(stubEngine2.runRulesCallHistory[0].runOptions, expectedEngineRunOptionsEngine2); + expect(fakeFileSystemHandler.dirWasCreated(expectedEngineRunOptionsEngine2.workingFolder)).toEqual(true); }); it("When the workspace provided is one that is not constructed from CodeAnalyzer's createWorkspace method, then it should still work", async () => { const dummyWorkspace: Workspace = new StubWorkspace(); + const workingDirectoriesRoot: string = path.join(os.tmpdir(), `code-analyzer`, `run-${fixedClock.formatToDateTimeString()}`); + await codeAnalyzer.run(selection, { workspace: dummyWorkspace }); @@ -232,6 +252,7 @@ describe("Tests for the run method of CodeAnalyzer", () => { ruleNames: expectedStubEngine1RuleNames, runOptions: { logFolder: codeAnalyzer.getConfig().getLogFolder(), + workingFolder: path.join(workingDirectoriesRoot, 'stubEngine1'), workspace: new engApi.Workspace(dummyWorkspace.getWorkspaceId(), dummyWorkspace.getRawFilesAndFolders(), dummyWorkspace.getRawTargets()) } @@ -242,8 +263,11 @@ describe("Tests for the run method of CodeAnalyzer", () => { selection = await codeAnalyzer.selectRules(['stubEngine1:Recommended']); await codeAnalyzer.run(selection, sampleRunOptions); + const workingDirectoriesRoot: string = path.join(os.tmpdir(), `code-analyzer`, `run-${fixedClock.formatToDateTimeString()}`); + const expectedEngineRunOptions: engApi.RunOptions = { logFolder: codeAnalyzer.getConfig().getLogFolder(), + workingFolder: path.join(workingDirectoriesRoot, 'stubEngine1'), workspace: new engApi.Workspace("FixedId", [__dirname]) }; expect(stubEngine1.runRulesCallHistory).toHaveLength(1); diff --git a/packages/code-analyzer-core/test/output-format.test.ts b/packages/code-analyzer-core/test/output-format.test.ts index f579ca95..fd780d76 100644 --- a/packages/code-analyzer-core/test/output-format.test.ts +++ b/packages/code-analyzer-core/test/output-format.test.ts @@ -5,7 +5,7 @@ import { RunResults, RunResultsImpl } from "../src/results"; import { RuleImpl, RuleSelection, RuleSelectionImpl } from "../src/rules"; import * as stubs from "./stubs"; import { FixedClock } from "@salesforce/code-analyzer-engine-api/utils"; -import { changeWorkingDirectoryToPackageRoot } from "./test-helpers"; +import {changeWorkingDirectoryToPackageRoot, FakeFileSystemHandler} from "./test-helpers"; import {SeverityLevel} from "@salesforce/code-analyzer-engine-api"; changeWorkingDirectoryToPackageRoot(); @@ -19,6 +19,7 @@ beforeAll(async () => { fixedTime = new Date(2024, 6, 3, 9, 14, 34, 567); codeAnalyzer._setClock(new FixedClock(fixedTime)); const stubPlugin: stubs.StubEnginePlugin = new stubs.StubEnginePlugin(); + codeAnalyzer._setFileSystemHandler(new FakeFileSystemHandler()); await codeAnalyzer.addEnginePlugin(stubPlugin); (stubPlugin.getCreatedEngine('stubEngine1') as stubs.StubEngine1).resultsToReturn = { violations: [ @@ -269,6 +270,7 @@ function getContentsOfExpectedOutputFile(expectedOutputFileName: string, escapeB async function createResultsWithUnexpectedError(): Promise { const codeAnalyzer: CodeAnalyzer = new CodeAnalyzer(CodeAnalyzerConfig.withDefaults()); codeAnalyzer._setClock(new FixedClock(fixedTime)); + codeAnalyzer._setFileSystemHandler(new FakeFileSystemHandler()); await codeAnalyzer.addEnginePlugin(new stubs.ThrowingEnginePlugin()); return codeAnalyzer.run(await codeAnalyzer.selectRules([]), {workspace: await codeAnalyzer.createWorkspace(['test'])}); } @@ -276,6 +278,7 @@ async function createResultsWithUnexpectedError(): Promise { async function createRulesWithEmptyTags(): Promise { const codeAnalyzer: CodeAnalyzer = new CodeAnalyzer(CodeAnalyzerConfig.withDefaults()); codeAnalyzer._setClock(new FixedClock(fixedTime)); + codeAnalyzer._setFileSystemHandler(new FakeFileSystemHandler()); await codeAnalyzer.addEnginePlugin(new stubs.EmptyTagEnginePlugin()); return codeAnalyzer.selectRules(['all']) } diff --git a/packages/code-analyzer-core/test/rule-selection.test.ts b/packages/code-analyzer-core/test/rule-selection.test.ts index bb3c21b1..71e724ec 100644 --- a/packages/code-analyzer-core/test/rule-selection.test.ts +++ b/packages/code-analyzer-core/test/rule-selection.test.ts @@ -16,7 +16,8 @@ import * as engApi from "@salesforce/code-analyzer-engine-api" import {FixedClock} from "@salesforce/code-analyzer-engine-api/utils"; import {RepeatedRuleNameEnginePlugin, StubEnginePlugin} from "./stubs"; import path from "node:path"; -import {changeWorkingDirectoryToPackageRoot, FixedUniqueIdGenerator} from "./test-helpers"; +import os from "node:os"; +import {changeWorkingDirectoryToPackageRoot, FakeFileSystemHandler, FixedUniqueIdGenerator} from "./test-helpers"; import {getMessage} from "../src/messages"; import * as stubs from "./stubs"; @@ -26,6 +27,8 @@ describe('Tests for selecting rules', () => { let codeAnalyzer: CodeAnalyzer; let plugin: StubEnginePlugin; let sampleTimestamp: Date; + let fixedClock: FixedClock; + let fakeFileSystemHandler: FakeFileSystemHandler; async function setupCodeAnalyzer(codeAnalyzer: CodeAnalyzer) : Promise { plugin = new StubEnginePlugin(); @@ -37,7 +40,10 @@ describe('Tests for selecting rules', () => { codeAnalyzer = new CodeAnalyzer(CodeAnalyzerConfig.withDefaults()); await setupCodeAnalyzer(codeAnalyzer); sampleTimestamp = new Date(); - codeAnalyzer._setClock(new FixedClock(sampleTimestamp)); + fixedClock = new FixedClock(sampleTimestamp); + codeAnalyzer._setClock(fixedClock); + fakeFileSystemHandler = new FakeFileSystemHandler(); + codeAnalyzer._setFileSystemHandler(fakeFileSystemHandler); }) it('When no rule selectors are provided then the Recommended tag is used', async () => { @@ -269,14 +275,25 @@ describe('Tests for selecting rules', () => { it('When selectRules is not provided with SelectOptions, then workspace should be undefined for all engines', async () => { await codeAnalyzer.selectRules(['all']); - const expectedDescribeOptions: engApi.DescribeOptions = { + const workingDirectoriesRoot: string = path.join(os.tmpdir(), `code-analyzer`, `describe-${fixedClock.formatToDateTimeString()}`); + + const expectedDescribeOptionsEngine1: engApi.DescribeOptions = { + logFolder: codeAnalyzer.getConfig().getLogFolder(), + workingFolder: path.join(workingDirectoriesRoot, 'stubEngine1'), + workspace: undefined + }; + const expectedDescribeOptionsEngine2: engApi.DescribeOptions = { logFolder: codeAnalyzer.getConfig().getLogFolder(), + workingFolder: path.join(workingDirectoriesRoot, 'stubEngine2'), workspace: undefined }; + expect(fakeFileSystemHandler.dirWasCreated(workingDirectoriesRoot)).toEqual(true); const stubEngine1: stubs.StubEngine1 = plugin.getCreatedEngine('stubEngine1') as stubs.StubEngine1; - expect(stubEngine1.describeRulesCallHistory).toEqual([{describeOptions: expectedDescribeOptions}]); + expect(stubEngine1.describeRulesCallHistory).toEqual([{describeOptions: expectedDescribeOptionsEngine1}]); + expect(fakeFileSystemHandler.dirWasCreated(expectedDescribeOptionsEngine1.workingFolder)).toEqual(true); const stubEngine2: stubs.StubEngine2 = plugin.getCreatedEngine('stubEngine2') as stubs.StubEngine2; - expect(stubEngine2.describeRulesCallHistory).toEqual([{describeOptions: expectedDescribeOptions}]); + expect(stubEngine2.describeRulesCallHistory).toEqual([{describeOptions: expectedDescribeOptionsEngine2}]); + expect(fakeFileSystemHandler.dirWasCreated(expectedDescribeOptionsEngine2.workingFolder)).toEqual(true); }); it('When selectRules is provided with SelectOptions, then they are forwarded to the engines', async () => { @@ -285,7 +302,7 @@ describe('Tests for selecting rules', () => { } await codeAnalyzer.selectRules(['all'], selectOptions); - const expectedDescribeOptions: engApi.DescribeOptions = { + const expectedDescribeOptions: Partial = { logFolder: codeAnalyzer.getConfig().getLogFolder(), workspace: new engApi.Workspace('FixedId', [path.resolve('src'), path.resolve('test')]) }; diff --git a/packages/code-analyzer-core/test/test-helpers.ts b/packages/code-analyzer-core/test/test-helpers.ts index 87b89f3b..21c2f047 100644 --- a/packages/code-analyzer-core/test/test-helpers.ts +++ b/packages/code-analyzer-core/test/test-helpers.ts @@ -1,6 +1,6 @@ import process from "node:process"; import path from "node:path"; -import {UniqueIdGenerator} from "../src/utils"; +import {FileSystemHandler, UniqueIdGenerator} from "../src/utils"; export function changeWorkingDirectoryToPackageRoot() { let original_working_directory: string; @@ -27,3 +27,23 @@ export class FixedUniqueIdGenerator implements UniqueIdGenerator { return "FixedUUID"; } } + +export class FakeFileSystemHandler implements FileSystemHandler { + private fsSet: Set = new Set(); + + directoryExists(absolutePath: string): boolean { + return this.fsSet.has(absolutePath); + } + + createDirectory(absolutePath: string): Promise { + if (this.fsSet.has(absolutePath)) { + throw new Error(`TEST ERROR: Path ${absolutePath} was created twice`); + } + this.fsSet.add(absolutePath); + return Promise.resolve(); + } + + dirWasCreated(absolutePath: string): boolean { + return this.fsSet.has(absolutePath); + } +} diff --git a/packages/code-analyzer-engine-api/package.json b/packages/code-analyzer-engine-api/package.json index 6c0eba0c..49ee7310 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.27.0", + "version": "0.28.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -17,7 +17,7 @@ "types": "dist/index.d.ts", "dependencies": { "@types/node": "^20.0.0", - "tmp": "^0.2.3" + "tmp": "^0.2.5" }, "devDependencies": { "@eslint/js": "^9.32.0", @@ -65,4 +65,4 @@ "!src/utils/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/code-analyzer-engine-api/src/engines.ts b/packages/code-analyzer-engine-api/src/engines.ts index 50727a27..19cf03cb 100644 --- a/packages/code-analyzer-engine-api/src/engines.ts +++ b/packages/code-analyzer-engine-api/src/engines.ts @@ -18,6 +18,12 @@ export type DescribeOptions = { */ logFolder: string + /** + * A temporary folder created specifically for use by the current engine. If the engine ever needs to create temporary + * files or folders, that should be done here. + */ + workingFolder: string + /** * The workspace may or may not be available. If available, then engines should use this workspace object to give a * more accurate list of which of the engine's rules are relevant to the targeted files in the workspace. That is @@ -41,6 +47,12 @@ export type RunOptions = { */ logFolder: string + /** + * A temporary folder created specifically for use by the current engine. If the engine ever needs to create temporary + * files or folders, that should be done here. + */ + workingFolder: string + /** * The workspace object specifying the files that make up the user's workspace and which files should be targeted. * 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..6dcb64a7 100644 --- a/packages/code-analyzer-engine-api/src/utils/fs-utils.ts +++ b/packages/code-analyzer-engine-api/src/utils/fs-utils.ts @@ -6,6 +6,10 @@ import fs from "node:fs"; tmp.setGracefulCleanup(); const tmpDirAsync = promisify((options: tmp.DirOptions, cb: tmp.DirCallback) => tmp.dir(options, cb)); +export async function createNamedTempDir(name: string, parentTempDir?: string): Promise { + return tmpDirAsync({name, dir: parentTempDir, keep: false, unsafeCleanup: true}); +} + /** * 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. diff --git a/packages/code-analyzer-engine-api/src/utils/index.ts b/packages/code-analyzer-engine-api/src/utils/index.ts index f761c135..6f22981f 100644 --- a/packages/code-analyzer-engine-api/src/utils/index.ts +++ b/packages/code-analyzer-engine-api/src/utils/index.ts @@ -5,6 +5,7 @@ export { } from './datetime-utils'; export { + createNamedTempDir, createTempDir, calculateLongestCommonParentFolderOf } from './fs-utils'; 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..fcf70f85 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,6 @@ import fs from 'node:fs'; import os from 'node:os'; -import {createTempDir, FixedClock, indent, JavaCommandExecutor, RealClock} from "../../src/utils"; +import {createTempDir, createNamedTempDir, FixedClock, indent, JavaCommandExecutor, RealClock} from "../../src/utils"; describe('Tests for Clock', () => { @@ -26,6 +26,15 @@ describe('Tests for Clock', () => { }); }) +describe('Tests for createNamedTempDir', () => { + it('Successfully creates named directory', async () => { + const timestamp: number = Date.now(); + const tmpDir: string = await createNamedTempDir(`tmp-${timestamp}`, os.tmpdir()); + + expect(fs.existsSync(tmpDir)).toEqual(true); + }); +}); + describe('Tests for createTempDir', () => { it('Successfully creates temporary directory', async () => { // Before testing, figure out how many entries are in the temp folder. diff --git a/packages/code-analyzer-eslint-engine/package.json b/packages/code-analyzer-eslint-engine/package.json index 5ee1c52c..e96bc77c 100644 --- a/packages/code-analyzer-eslint-engine/package.json +++ b/packages/code-analyzer-eslint-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-eslint-engine", "description": "Plugin package that adds 'eslint' as an engine into Salesforce Code Analyzer", - "version": "0.30.0", + "version": "0.31.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -17,8 +17,8 @@ "@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.27.0", - "@salesforce/code-analyzer-eslint8-engine": "0.5.0", + "@salesforce/code-analyzer-engine-api": "0.28.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", "@types/node": "^20.0.0", @@ -77,4 +77,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/code-analyzer-eslint8-engine/package.json b/packages/code-analyzer-eslint8-engine/package.json index 9d467d8d..73804633 100644 --- a/packages/code-analyzer-eslint8-engine/package.json +++ b/packages/code-analyzer-eslint8-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-eslint8-engine", "description": "Plugin package that adds 'eslint' (version 8) as an engine into Salesforce Code Analyzer", - "version": "0.5.0", + "version": "0.6.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -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.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@salesforce/eslint-config-lwc": "3.7.2", "@salesforce/eslint-plugin-lightning": "1.0.1", "@types/node": "^20.0.0", @@ -75,4 +75,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/code-analyzer-flow-engine/package.json b/packages/code-analyzer-flow-engine/package.json index 7a546391..c912fa73 100644 --- a/packages/code-analyzer-flow-engine/package.json +++ b/packages/code-analyzer-flow-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-flow-engine", "description": "Plugin package that adds 'Flow Scanner' as an engine into Salesforce Code Analyzer", - "version": "0.25.0", + "version": "0.26.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -63,4 +63,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/code-analyzer-pmd-engine/package.json b/packages/code-analyzer-pmd-engine/package.json index c24ac80e..6bb28ca3 100644 --- a/packages/code-analyzer-pmd-engine/package.json +++ b/packages/code-analyzer-pmd-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-pmd-engine", "description": "Plugin package that adds 'pmd' and 'cpd' as engines into Salesforce Code Analyzer", - "version": "0.29.0", + "version": "0.30.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -71,4 +71,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/code-analyzer-regex-engine/package.json b/packages/code-analyzer-regex-engine/package.json index 92fe4855..e874783f 100644 --- a/packages/code-analyzer-regex-engine/package.json +++ b/packages/code-analyzer-regex-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-regex-engine", "description": "Plugin package that adds 'regex' as an engine into Salesforce Code Analyzer", - "version": "0.26.0", + "version": "0.27.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.4", "p-limit": "^3.1.0" @@ -62,4 +62,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/code-analyzer-retirejs-engine/package.json b/packages/code-analyzer-retirejs-engine/package.json index c681883a..ce130089 100644 --- a/packages/code-analyzer-retirejs-engine/package.json +++ b/packages/code-analyzer-retirejs-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-retirejs-engine", "description": "Plugin package that adds 'retire-js' as an engine into Salesforce Code Analyzer", - "version": "0.25.0", + "version": "0.26.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "isbinaryfile": "^5.0.4", "node-stream-zip": "^1.15.0", @@ -65,4 +65,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/code-analyzer-sfge-engine/package.json b/packages/code-analyzer-sfge-engine/package.json index c367b111..2bd7a922 100644 --- a/packages/code-analyzer-sfge-engine/package.json +++ b/packages/code-analyzer-sfge-engine/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/code-analyzer-sfge-engine", "description": "Plugin package that adds 'Salesforce Graph Engine' as an engine into Salesforce Code Analyzer", - "version": "0.11.0", + "version": "0.12.0-SNAPSHOT", "author": "The Salesforce Code Analyzer Team", "license": "BSD-3-Clause", "homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview", @@ -13,7 +13,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { - "@salesforce/code-analyzer-engine-api": "0.27.0", + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT", "@types/node": "^20.0.0", "semver": "^7.7.2" }, @@ -71,4 +71,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +} diff --git a/packages/code-analyzer-stylelint-engine/package.json b/packages/code-analyzer-stylelint-engine/package.json index 8c6c241a..84b6ad77 100644 --- a/packages/code-analyzer-stylelint-engine/package.json +++ b/packages/code-analyzer-stylelint-engine/package.json @@ -14,7 +14,7 @@ "types": "dist/index.d.ts", "dependencies": { "@types/node": "^20.0.0", - "@salesforce/code-analyzer-engine-api": "0.27.0" + "@salesforce/code-analyzer-engine-api": "0.28.0-SNAPSHOT" }, "devDependencies": { "@eslint/js": "^9.32.0", @@ -60,4 +60,4 @@ "!src/index.ts" ] } -} \ No newline at end of file +}