Skip to content

Commit 40986bf

Browse files
FIX: @W-18403733@: Pass in raw selection to be scanned instead of expanding… (#235)
1 parent df2ded3 commit 40986bf

File tree

12 files changed

+179
-75
lines changed

12 files changed

+179
-75
lines changed

src/extension.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {CliCommandExecutor, CliCommandExecutorImpl} from "./lib/cli-commands";
3333
import {getErrorMessage} from "./lib/utils";
3434
import {FileHandler, FileHandlerImpl} from "./lib/fs-utils";
3535
import {VscodeWorkspace, VscodeWorkspaceImpl, WindowManager, WindowManagerImpl} from "./lib/vscode-api";
36+
import {Workspace} from "./lib/workspace";
3637

3738

3839
// Object to hold the state of our extension for a specific activation context, to be returned by our activate function
@@ -85,13 +86,14 @@ export async function activate(context: vscode.ExtensionContext): Promise<SFCAEx
8586
context.subscriptions.push(scanManager);
8687

8788
const windowManager: WindowManager = new WindowManagerImpl(outputChannel);
89+
const vscodeWorkspace: VscodeWorkspace = new VscodeWorkspaceImpl();
8890

8991
const taskWithProgressRunner: TaskWithProgressRunner = new TaskWithProgressRunnerImpl();
9092

93+
9194
const cliCommandExecutor: CliCommandExecutor = new CliCommandExecutorImpl(logger);
92-
const vscodeWorkspace: VscodeWorkspace = new VscodeWorkspaceImpl();
9395
const fileHandler: FileHandler = new FileHandlerImpl();
94-
const codeAnalyzer: CodeAnalyzer = new CodeAnalyzerImpl(cliCommandExecutor, settingsManager, display, vscodeWorkspace, fileHandler);
96+
const codeAnalyzer: CodeAnalyzer = new CodeAnalyzerImpl(cliCommandExecutor, settingsManager, display, fileHandler);
9597
const dfaRunner: DfaRunner = new DfaRunner(context, codeAnalyzer, telemetryService, logger); // This thing is really old and clunky. It'll go away when we remove v4 stuff. But if we don't want to wait we could move all this into the v4-scanner.ts file
9698
context.subscriptions.push(dfaRunner);
9799

@@ -126,7 +128,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<SFCAEx
126128
vscode.window.showWarningMessage(messages.noActiveEditor);
127129
return;
128130
}
129-
return codeAnalyzerRunAction.run(Constants.COMMAND_RUN_ON_ACTIVE_FILE, [document.fileName]);
131+
const workspace: Workspace = await Workspace.fromTargetPaths([document.fileName], vscodeWorkspace, fileHandler);
132+
return codeAnalyzerRunAction.run(Constants.COMMAND_RUN_ON_ACTIVE_FILE, workspace);
130133
});
131134

132135
// "Analyze On Open" and "Analyze on Save" functionality:
@@ -139,7 +142,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<SFCAEx
139142
const isValidFileThatHasNotBeenScannedYet = isValidFile && !scanManager.haveAlreadyScannedFile(editor.document.fileName);
140143
if (isValidFileThatHasNotBeenScannedYet) {
141144
scanManager.addFileToAlreadyScannedFiles(editor.document.fileName);
142-
await codeAnalyzerRunAction.run(Constants.COMMAND_RUN_ON_ACTIVE_FILE, [editor.document.fileName]);
145+
const workspace: Workspace = await Workspace.fromTargetPaths([editor.document.fileName], vscodeWorkspace, fileHandler);
146+
await codeAnalyzerRunAction.run(Constants.COMMAND_RUN_ON_ACTIVE_FILE, workspace);
143147
}
144148
});
145149
onDidSaveTextDocument(async (document: vscode.TextDocument) => {
@@ -154,22 +158,20 @@ export async function activate(context: vscode.ExtensionContext): Promise<SFCAEx
154158

155159
if (settingsManager.getAnalyzeOnSave()) {
156160
scanManager.addFileToAlreadyScannedFiles(document.fileName);
157-
await codeAnalyzerRunAction.run(Constants.COMMAND_RUN_ON_ACTIVE_FILE, [document.fileName]);
161+
const workspace: Workspace = await Workspace.fromTargetPaths([document.fileName], vscodeWorkspace, fileHandler);
162+
await codeAnalyzerRunAction.run(Constants.COMMAND_RUN_ON_ACTIVE_FILE, workspace);
158163
}
159164
});
160165

161166
// COMMAND_RUN_ON_SELECTED: Invokable by 'explorer/context' menu always. Uses v4 instead of v5 when 'sfca.codeAnalyzerV4Enabled'.
162167
registerCommand(Constants.COMMAND_RUN_ON_SELECTED, async (singleSelection: vscode.Uri, multiSelection?: vscode.Uri[]) => {
163168
const selection: vscode.Uri[] = (multiSelection && multiSelection.length > 0) ? multiSelection : [singleSelection];
164-
// TODO: We may wish to consider moving away from this target resolution, and just passing in files and folders
165-
// as given to us. It's possible the current style could lead to overflowing the CLI when a folder has
166-
// many files.
167-
const selectedFiles: string[] = await targeting.getFilesFromSelection(selection);
168-
if (selectedFiles.length == 0) { // I have not found a way to hit this, but we should check just in case
169+
const workspace: Workspace = await Workspace.fromTargetPaths(selection.map(uri => uri.fsPath), vscodeWorkspace, fileHandler);
170+
if (workspace.getRawTargetPaths().length == 0) { // I have not found a way to hit this, but we should check just in case
169171
vscode.window.showWarningMessage(messages.targeting.error.noFileSelected);
170172
return;
171173
}
172-
await codeAnalyzerRunAction.run(Constants.COMMAND_RUN_ON_SELECTED, selectedFiles);
174+
await codeAnalyzerRunAction.run(Constants.COMMAND_RUN_ON_SELECTED, workspace);
173175
});
174176

175177

src/lib/cli-commands.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,20 +96,30 @@ export class CliCommandExecutorImpl implements CliCommandExecutor {
9696

9797
async exec(command: string, args: string[], options: ExecOptions = {}): Promise<CommandOutput> {
9898
return new Promise((resolve) => {
99-
const childProcess: cp.ChildProcessWithoutNullStreams = cp.spawn(command, args, {
100-
shell: process.platform.startsWith('win'), // Use shell on Windows machines
101-
});
102-
103-
if (options.pidHandler) {
104-
options.pidHandler(childProcess.pid);
105-
}
106-
const logLevel: vscode.LogLevel = options.logLevel === undefined ? vscode.LogLevel.Trace : options.logLevel;
107-
10899
const output: CommandOutput = {
109100
stdout: '',
110101
stderr: '',
111102
exitCode: 0
112103
};
104+
105+
let childProcess: cp.ChildProcessWithoutNullStreams;
106+
try {
107+
childProcess = cp.spawn(command, args, {
108+
shell: process.platform.startsWith('win'), // Use shell on Windows machines
109+
});
110+
} catch (err) {
111+
this.logger.logAtLevel(vscode.LogLevel.Error, `Failed to execute the following command:\n` +
112+
indent(`${command} ${args.map(arg => arg.includes(' ') ? `"${arg}"` : arg).join(' ')}`) + `\n\n` +
113+
'Error Thrown:\n' + indent(getErrorMessageWithStack(err)));
114+
output.stderr = getErrorMessageWithStack(err);
115+
output.exitCode = 127;
116+
resolve(output);
117+
}
118+
119+
if (options.pidHandler) {
120+
options.pidHandler(childProcess.pid);
121+
}
122+
const logLevel: vscode.LogLevel = options.logLevel === undefined ? vscode.LogLevel.Trace : options.logLevel;
113123
let combinedOut: string = '';
114124

115125
this.logger.logAtLevel(logLevel, `Executing with background process (${childProcess.pid}):\n` +

src/lib/code-analyzer-run-action.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {Display} from "./display";
99
import {getErrorMessage, getErrorMessageWithStack} from "./utils";
1010
import {ProgressReporter, TaskWithProgressRunner} from "./progress";
1111
import {WindowManager} from "./vscode-api";
12+
import {Workspace} from "./workspace";
1213

1314
export const UNINSTANTIABLE_ENGINE_RULE = 'UninstantiableEngineError';
1415

@@ -35,9 +36,9 @@ export class CodeAnalyzerRunAction {
3536
/**
3637
* Runs the scanner against the specified file and displays the results.
3738
* @param commandName The command being run
38-
* @param filesToScan The files to run against
39+
* @param workspace The workspace to run against
3940
*/
40-
run(commandName: string, filesToScan: string[]): Promise<void> {
41+
run(commandName: string, workspace: Workspace): Promise<void> {
4142
return this.taskWithProgressRunner.runTask(async (progressReporter: ProgressReporter) => {
4243
const startTime: number = Date.now();
4344

@@ -59,7 +60,7 @@ export class CodeAnalyzerRunAction {
5960
increment: 20
6061
});
6162
this.logger.log(messages.info.scanningWith(await this.codeAnalyzer.getScannerName()));
62-
const violations: Violation[] = await this.codeAnalyzer.scan(filesToScan);
63+
const violations: Violation[] = await this.codeAnalyzer.scan(workspace);
6364

6465
progressReporter.reportProgress({
6566
message: messages.scanProgressReport.processingResults,
@@ -78,9 +79,10 @@ export class CodeAnalyzerRunAction {
7879
});
7980

8081
const diagnostics: CodeAnalyzerDiagnostic[] = violationsWithFileLocation.map(v => CodeAnalyzerDiagnostic.fromViolation(v));
81-
this.diagnosticManager.clearDiagnosticsForFiles(filesToScan.map(f => vscode.Uri.file(f)));
82+
const targetedFiles: string[] = await workspace.getTargetedFiles();
83+
this.diagnosticManager.clearDiagnosticsForFiles(targetedFiles.map(f => vscode.Uri.file(f)));
8284
this.diagnosticManager.addDiagnostics(diagnostics);
83-
void this.displayResults(filesToScan.length, violationsWithFileLocation);
85+
void this.displayResults(targetedFiles.length, violationsWithFileLocation);
8486

8587
this.telemetryService.sendCommandEvent(Constants.TELEM_SUCCESSFUL_STATIC_ANALYSIS, {
8688
commandName: commandName,

src/lib/code-analyzer.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
RECOMMENDED_MINIMUM_REQUIRED_CODE_ANALYZER_CLI_PLUGIN_VERSION
1212
} from "./constants";
1313
import {CliScannerStrategy} from "./scanner-strategies/scanner-strategy";
14-
import {VscodeWorkspace, VscodeWorkspaceImpl} from "./vscode-api";
1514
import {FileHandler, FileHandlerImpl} from "./fs-utils";
15+
import {Workspace} from "./workspace";
1616

1717
export interface CodeAnalyzer extends CliScannerStrategy {
1818
validateEnvironment(): Promise<void>;
@@ -22,7 +22,6 @@ export class CodeAnalyzerImpl implements CodeAnalyzer {
2222
private readonly cliCommandExecutor: CliCommandExecutor;
2323
private readonly settingsManager: SettingsManager;
2424
private readonly display: Display;
25-
private readonly vscodeWorkspace: VscodeWorkspace;
2625
private readonly fileHandler: FileHandler
2726

2827
private cliIsInstalled: boolean = false;
@@ -31,11 +30,10 @@ export class CodeAnalyzerImpl implements CodeAnalyzer {
3130
private codeAnalyzerV5?: CliScannerV5Strategy;
3231

3332
constructor(cliCommandExecutor: CliCommandExecutor, settingsManager: SettingsManager, display: Display,
34-
vscodeWorkspace: VscodeWorkspace = new VscodeWorkspaceImpl(), fileHandler: FileHandler = new FileHandlerImpl()) {
33+
fileHandler: FileHandler = new FileHandlerImpl()) {
3534
this.cliCommandExecutor = cliCommandExecutor;
3635
this.settingsManager = settingsManager;
3736
this.display = display;
38-
this.vscodeWorkspace = vscodeWorkspace;
3937
this.fileHandler = fileHandler;
4038
}
4139

@@ -87,11 +85,11 @@ export class CodeAnalyzerImpl implements CodeAnalyzer {
8785
this.display.displayWarning(messages.codeAnalyzer.usingOlderVersion(installedVersion.toString(), recommendedMinVersion.toString()) + '\n'
8886
+ messages.codeAnalyzer.installLatestVersion);
8987
}
90-
this.codeAnalyzerV5 = new CliScannerV5Strategy(installedVersion, this.cliCommandExecutor, this.settingsManager, this.vscodeWorkspace, this.fileHandler);
88+
this.codeAnalyzerV5 = new CliScannerV5Strategy(installedVersion, this.cliCommandExecutor, this.settingsManager, this.fileHandler);
9189
}
9290

93-
async scan(filesToScan: string[]): Promise<Violation[]> {
94-
return (await this.getDelegate()).scan(filesToScan);
91+
async scan(workspace: Workspace): Promise<Violation[]> {
92+
return (await this.getDelegate()).scan(workspace);
9593
}
9694

9795
async getScannerName(): Promise<string> {

src/lib/scanner-strategies/scanner-strategy.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {Violation} from '../diagnostics';
2+
import {Workspace} from "../workspace";
23

34
export interface CliScannerStrategy {
4-
scan(filesToScan: string[]): Promise<Violation[]>;
5+
scan(workspace: Workspace): Promise<Violation[]>;
56

67
getScannerName(): Promise<string>;
78

src/lib/scanner-strategies/v4-scanner.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {SettingsManager} from "../settings";
66
import * as semver from 'semver';
77
import {CliCommandExecutor, CommandOutput} from "../cli-commands";
88
import {FileHandler} from "../fs-utils";
9+
import {Workspace} from "../workspace";
910

1011
export type BaseV4Violation = {
1112
ruleName: string;
@@ -66,9 +67,9 @@ export class CliScannerV4Strategy implements CliScannerStrategy {
6667
return Promise.resolve(`@salesforce/sfdx-scanner@${this.version.toString()} via CLI`);
6768
}
6869

69-
public async scan(filesToScan: string[]): Promise<Violation[]> {
70+
public async scan(workspace: Workspace): Promise<Violation[]> {
7071
// Create the arg array.
71-
const args: string[] = await this.createArgArray(filesToScan);
72+
const args: string[] = await this.createArgArray(workspace);
7273

7374
// Invoke the scanner.
7475
const executionResult: V4ExecutionResult = await this.invokeAnalyzer(args);
@@ -77,7 +78,7 @@ export class CliScannerV4Strategy implements CliScannerStrategy {
7778
return this.processResults(executionResult);
7879
}
7980

80-
private async createArgArray(targets: string[]): Promise<string[]> {
81+
private async createArgArray(workspace: Workspace): Promise<string[]> {
8182
const engines: string = this.settingsManager.getEnginesToRun();
8283
const pmdCustomConfigFile: string | undefined = this.settingsManager.getPmdCustomConfigFile();
8384
const rulesCategory: string | undefined = this.settingsManager.getRulesCategory();
@@ -89,7 +90,7 @@ export class CliScannerV4Strategy implements CliScannerStrategy {
8990

9091
const args: string[] = [
9192
'scanner', 'run',
92-
'--target', `${targets.join(',')}`,
93+
'--target', `${workspace.getRawTargetPaths().join(',')}`,
9394
`--engine`, engines,
9495
`--json`
9596
];

src/lib/scanner-strategies/v5-scanner.ts

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as path from 'node:path';
77
import * as semver from 'semver';
88
import {SettingsManager} from "../settings";
99
import {CliCommandExecutor, CommandOutput} from "../cli-commands";
10-
import {VscodeWorkspace} from "../vscode-api";
10+
import {Workspace} from "../workspace";
1111

1212
type ResultsJson = {
1313
runDir: string;
@@ -31,16 +31,14 @@ export class CliScannerV5Strategy implements CliScannerStrategy {
3131
private readonly version: semver.SemVer;
3232
private readonly cliCommandExecutor: CliCommandExecutor;
3333
private readonly settingsManager: SettingsManager;
34-
private readonly vscodeWorkspace: VscodeWorkspace;
3534
private readonly fileHandler: FileHandler;
3635

3736
private ruleDescriptionMap?: Map<string, string>;
3837

39-
public constructor(version: semver.SemVer, cliCommandExecutor: CliCommandExecutor, settingsManager: SettingsManager, vscodeWorkspace: VscodeWorkspace, fileHandler: FileHandler) {
38+
public constructor(version: semver.SemVer, cliCommandExecutor: CliCommandExecutor, settingsManager: SettingsManager, fileHandler: FileHandler) {
4039
this.version = version;
4140
this.cliCommandExecutor = cliCommandExecutor;
4241
this.settingsManager = settingsManager;
43-
this.vscodeWorkspace = vscodeWorkspace;
4442
this.fileHandler = fileHandler;
4543
}
4644

@@ -63,25 +61,18 @@ export class CliScannerV5Strategy implements CliScannerStrategy {
6361
return this.ruleDescriptionMap;
6462
}
6563

66-
public async scan(filesToScan: string[]): Promise<Violation[]> {
64+
public async scan(workspace: Workspace): Promise<Violation[]> {
6765
const ruleSelector: string = this.settingsManager.getCodeAnalyzerRuleSelectors();
6866
const configFile: string = this.settingsManager.getCodeAnalyzerConfigFile();
6967

7068
const args: string[] = ['code-analyzer', 'run'];
7169

7270
if (semver.gte(this.version, '5.0.0')) {
73-
// Just in case a file is open in the editor that does not live in the current workspace, or if there
74-
// is no workspace open at all, we still want to be able to run code analyzer without error, so we
75-
// include the files to scan always along with any workspace folders.
76-
const workspacePaths: string[] = [
77-
...this.vscodeWorkspace.getWorkspaceFolders(),
78-
...filesToScan
79-
]
80-
workspacePaths.forEach(p => args.push('-w', p));
81-
filesToScan.forEach(p => args.push('-t', p));
71+
workspace.getRawWorkspacePaths().forEach(p => args.push('-w', p));
72+
workspace.getRawTargetPaths().forEach(p => args.push('-t', p));
8273
} else {
83-
// Before 5.0.0 the --target flag did not exist, so we just make the workspace equal to the files to scan
84-
filesToScan.forEach(p => args.push('-w', p));
74+
// Before 5.0.0 the --target flag did not exist, so we just make the workspace equal to the target paths
75+
workspace.getRawTargetPaths().forEach(p => args.push('-w', p));
8576
}
8677

8778
if (ruleSelector) {

src/lib/workspace.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import * as vscode from "vscode";
2+
import {FileHandler} from "./fs-utils";
3+
import {messages} from "./messages";
4+
import {glob} from "glob";
5+
import {VscodeWorkspace} from "./vscode-api";
6+
7+
// Note, calling this Workspace since the future we might make this close and closer like what we have in Core and
8+
// eventually replace it when we no longer depend on the CLI.
9+
export class Workspace {
10+
private readonly rawTargets: string[];
11+
private readonly vscodeWorkspace: VscodeWorkspace;
12+
private readonly fileHandler: FileHandler;
13+
14+
private constructor(rawTargets: string[], vscodeWorkspace: VscodeWorkspace, fileHandler: FileHandler) {
15+
this.rawTargets = rawTargets;
16+
this.vscodeWorkspace = vscodeWorkspace;
17+
this.fileHandler = fileHandler;
18+
}
19+
20+
static async fromTargetPaths(targetedPaths: string[], vscodeWorkspace: VscodeWorkspace, fileHandler: FileHandler): Promise<Workspace> {
21+
const uniqueTargetPaths: Set<string> = new Set();
22+
for (const target of targetedPaths) {
23+
if (!(await fileHandler.exists(target))) {
24+
// This should never happen, but we should handle it gracefully regardless.
25+
throw new Error(messages.targeting.error.nonexistentSelectedFileGenerator(target));
26+
}
27+
uniqueTargetPaths.add(target);
28+
}
29+
return new Workspace([...uniqueTargetPaths], vscodeWorkspace, fileHandler);
30+
}
31+
32+
/**
33+
* Unique string array of targeted files and folders as they were selected by the user
34+
*/
35+
getRawTargetPaths(): string[] {
36+
return this.rawTargets;
37+
}
38+
39+
/**
40+
* Unique array of files and folders that make up the workspace.
41+
*
42+
* Just in case a file is open in the editor that does not live in the current workspace, or if there
43+
* is no workspace open at all, we still want to be able to run code analyzer without error, so we
44+
* include the raw targeted files and folders always along with any vscode workspace folders.
45+
*/
46+
getRawWorkspacePaths(): string[] {
47+
return [... new Set([
48+
...this.vscodeWorkspace.getWorkspaceFolders(),
49+
...this.getRawTargetPaths()
50+
])];
51+
}
52+
53+
/**
54+
* String array of expanded files that make up the targeted files
55+
* This array is derived by expanding the targeted folders recursively into their children files.
56+
*/
57+
async getTargetedFiles(): Promise<string[]> {
58+
const workspaceFiles: string[] = [];
59+
for (const fileOrFolder of this.getRawTargetPaths()) {
60+
if (await this.fileHandler.isDir(fileOrFolder)) {
61+
// Globby wants forward-slashes, but Windows uses back-slashes, so always convert to forward slashes
62+
const globbablePath: string = fileOrFolder.replace(/\\/g, '/');
63+
const globOut: string[] = await glob(`${globbablePath}/**/*`, {nodir: true});
64+
// Globby's results are Unix-formatted. Do a Uri.file round-trip to return the path to its expected form.
65+
globOut.forEach(o => workspaceFiles.push(vscode.Uri.file(o).fsPath));
66+
} else {
67+
workspaceFiles.push(fileOrFolder);
68+
}
69+
}
70+
return workspaceFiles;
71+
}
72+
}

0 commit comments

Comments
 (0)