Skip to content

Commit

Permalink
NEW (Extension) @W-15639759@ Introduce a new command to run graph eng…
Browse files Browse the repository at this point in the history
…ine at the project level
  • Loading branch information
jag-j committed Jul 3, 2024
1 parent ac88a31 commit 243633f
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 21 deletions.
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@
{
"command": "sfca.removeDiagnosticsOnSelectedFile",
"title": "SFDX: Clear Code Analyzer violations from selected files or folders"
},
{
"command": "sfca.runDfa",
"title": "***SFDX: Run Graph-Engine Based Analysis***"
}
],
"configuration": {
Expand Down Expand Up @@ -162,6 +166,10 @@
"command": "sfca.runDfaOnSelectedMethod",
"when": "false"
},
{
"command": "sfca.runDfa",
"when": "false"
},
{
"command": "sfca.removeDiagnosticsOnActiveFile",
"when": "true"
Expand Down
88 changes: 77 additions & 11 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@ export async function activate(context: vscode.ExtensionContext): Promise<vscode
// It is possible that the cache was not cleared when VS Code exited the last time. Just to be on the safe side, we clear the DFA process cache at activation.
void context.workspaceState.update(Constants.WORKSPACE_DFA_PROCESS, undefined);

const runDfaOnSelectedMethod = vscode.commands.registerCommand(Constants.COMMAND_RUN_DFA_ON_SELECTED_METHOD, async () => {
const runDfaOnSelectedMethodCmd = vscode.commands.registerCommand(Constants.COMMAND_RUN_DFA_ON_SELECTED_METHOD, async () => {
if (await _shouldProceedWithDfaRun(context)) {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Window,
title: messages.graphEngine.spinnerText,
cancellable: true
}, (progress, token) => {
}, async (progress, token) => {
token.onCancellationRequested(async () => {
await _stopExistingDfaRun(context, outputChannel);
});
Expand All @@ -120,19 +120,90 @@ export async function activate(context: vscode.ExtensionContext): Promise<vscode
await vscode.window.showInformationMessage(messages.graphEngine.noViolationsFound);
return;
});
const methodLevelTarget: string = await targeting.getSelectedMethod();
// Pull out the file from the target and use it to identify the project directory.
const currentFile: string = methodLevelTarget.substring(0, methodLevelTarget.lastIndexOf('#'));
const projectDir: string = targeting.getProjectDir(currentFile);

return _runAndDisplayDfa(context, {
commandName: Constants.COMMAND_RUN_DFA_ON_SELECTED_METHOD,
outputChannel
}, customCancellationToken);
}, customCancellationToken, methodLevelTarget, projectDir);
});
}
});
context.subscriptions.push(runOnActiveFile, runOnSelected, runDfaOnSelectedMethod, removeDiagnosticsOnActiveFile, removeDiagnosticsOnSelectedFile, removeDiagnosticsInRange);

const runDfaOnWorkspaceCmd = vscode.commands.registerCommand(Constants.COMMAND_RUN_DFA, async () => {
await _runDfa(context, outputChannel);
});
context.subscriptions.push(runOnActiveFile, runOnSelected, runDfaOnSelectedMethodCmd, runDfaOnWorkspaceCmd, removeDiagnosticsOnActiveFile, removeDiagnosticsOnSelectedFile, removeDiagnosticsInRange);
TelemetryService.sendExtensionActivationEvent(extensionHrStart);
outputChannel.appendLine(`Extension sfdx-code-analyzer-vscode activated.`);
return Promise.resolve(context);
}

async function _runDfa(context: vscode.ExtensionContext, outputChannel: vscode.LogOutputChannel) {
const choice = await vscode.window.showQuickPick(
["***Yes***", "***No***"],
{
placeHolder: "***Re-run previously failed violations only?***",
canPickMany: false,
ignoreFocusOut: true
}
);

// Default to "Yes" if no choice is made
const rerunFailedOnly = choice !== "Yes";

if (!rerunFailedOnly) {
void vscode.window.showWarningMessage('***A full run of the graph engine will happen in the background. You can cancel this by clicking on the status progress.***');
await runDfaOnWorkspace(context, outputChannel);
} else if (!violationsCacheExists()) {
void vscode.window.showWarningMessage('***A full run of the graph engine will happen in the background since no existing cache found. You can cancel this by clicking on the status progress.***');
await runDfaOnWorkspace(context, outputChannel);
} else {
// Do nothing for now. This will be implemented as part of W-15639759
}
}

async function runDfaOnWorkspace(context: vscode.ExtensionContext, outputChannel: vscode.LogOutputChannel) {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Window,
title: messages.graphEngine.spinnerText,
cancellable: true
}, async (progress, token) => {
token.onCancellationRequested(async () => {
await _stopExistingDfaRun(context, outputChannel);
});
customCancellationToken = new vscode.CancellationTokenSource();
customCancellationToken.token.onCancellationRequested(async () => {
customCancellationToken?.dispose();
customCancellationToken = null;
await vscode.window.showInformationMessage(messages.graphEngine.noViolationsFound);
return;
});
// Pull out the file from the target and use it to identify the project directory.
const projectDir: string[] = vscode.workspace.workspaceFolders?.map(folder => folder.uri.path);

if (projectDir.length === 0) {
void vscode.window.showWarningMessage('***No project directory could be identified. Not proceeding with DFA run.***');
return;
}

// We only have one project loaded on VSCode at once. So, projectDir should have only one entry and we use
// the root directory of that project as the projectDir argument to run DFA.
return _runAndDisplayDfa(context, {
commandName: Constants.COMMAND_RUN_DFA_ON_SELECTED_METHOD,
outputChannel,
}, customCancellationToken, null, projectDir[0]);
});
}

function violationsCacheExists() {
// Returns false for now. Actual cache check will be performed as part of W-15639759.
return false;
}

export function _removeDiagnosticsInRange(uri: vscode.Uri, range: vscode.Range, diagnosticCollection: vscode.DiagnosticCollection) {
const currentDiagnostics = diagnosticCollection.get(uri) || [];
const updatedDiagnostics = filterOutDiagnosticsInRange(currentDiagnostics, range);
Expand Down Expand Up @@ -273,20 +344,15 @@ export async function _runAndDisplayPathless(selections: vscode.Uri[], runInfo:
* @param runInfo.outputChannel The output channel where information should be logged as needed
* @param runInfo.commandName The specific command being run
*/
export async function _runAndDisplayDfa(context:vscode.ExtensionContext ,runInfo: RunInfo, cancelToken: vscode.CancellationTokenSource): Promise<void> {
export async function _runAndDisplayDfa(context:vscode.ExtensionContext ,runInfo: RunInfo, cancelToken: vscode.CancellationTokenSource, methodLevelTarget: string, projectDir: string): Promise<void> {
const {
outputChannel,
commandName
} = runInfo;
const startTime = Date.now();
try {
await verifyPluginInstallation();
// Get the targeted method.
const methodLevelTarget: string = await targeting.getSelectedMethod();
// Pull out the file from the target and use it to identify the project directory.
const currentFile: string = methodLevelTarget.substring(0, methodLevelTarget.lastIndexOf('#'));
const projectDir: string = targeting.getProjectDir(currentFile);
const results: string = await new ScanRunner().runDfa([methodLevelTarget], projectDir, context);
const results = await new ScanRunner().runDfa([methodLevelTarget], projectDir, context);
if (results.length > 0) {
const panel = vscode.window.createWebviewPanel(
'dfaResults',
Expand Down
1 change: 1 addition & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const EXTENSION_PACK_ID = 'salesforce.salesforcedx-vscode';
export const COMMAND_RUN_ON_ACTIVE_FILE = 'sfca.runOnActiveFile';
export const COMMAND_RUN_ON_SELECTED = 'sfca.runOnSelected';
export const COMMAND_RUN_DFA_ON_SELECTED_METHOD = 'sfca.runDfaOnSelectedMethod';
export const COMMAND_RUN_DFA = 'sfca.runDfa';
export const COMMAND_REMOVE_DIAGNOSTICS_ON_ACTIVE_FILE = 'sfca.removeDiagnosticsOnActiveFile';
export const COMMAND_REMOVE_DIAGNOSTICS_ON_SELECTED_FILE = 'sfca.removeDiagnosticsOnSelectedFile';
export const COMMAND_DIAGNOSTICS_IN_RANGE = 'sfca.removeDiagnosticsInRange'
Expand Down
6 changes: 5 additions & 1 deletion src/lib/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ export class ScanRunner {
private createDfaArgArray(targets: string[], projectDir: string): string[] {
const args: string[] = [
'scanner', 'run', 'dfa',
'--target', `${targets.join(',')}`,
`--projectdir`, projectDir,
// NOTE: For now, we're using HTML output since it's the easiest to display to the user.
// This is exceedingly likely to change as we refine and polish the extension.
Expand All @@ -69,6 +68,11 @@ export class ScanRunner {
// implementation, but we may wish to rethink this in the future as we polish things.
`--json`
];

if (targets && targets.filter(target => target != null).length > 0) {
args.push('--target', `${targets.join(',')}`);
}

// There are a number of custom settings that we need to check too.
// First we should check whether warning violations are disabled.
if (SettingsManager.getGraphEngineDisableWarningViolations()) {
Expand Down
4 changes: 2 additions & 2 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ suite('Extension Test Suite', () => {
await _runAndDisplayDfa(null, {
commandName: fakeTelemetryName,
outputChannel
}, null);
}, null, 'someMethod', 'some/project/dir');

// ===== ASSERTIONS =====
Sinon.assert.callCount(errorSpy, 1);
Expand All @@ -272,7 +272,7 @@ suite('Extension Test Suite', () => {
await _runAndDisplayDfa(null, {
commandName: fakeTelemetryName,
outputChannel
}, null);
}, null, 'someMethod', 'some/project/dir');
} catch (e) {
err = e;
}
Expand Down
51 changes: 44 additions & 7 deletions src/test/suite/scanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,13 +240,14 @@ suite('ScanRunner', () => {
expect(args[0]).to.equal('scanner', 'Wrong arg');
expect(args[1]).to.equal('run', 'Wrong arg');
expect(args[2]).to.equal('dfa', 'Wrong arg');
expect(args[3]).to.equal('--target', 'Wrong arg');
expect(args[4]).to.equal(targets.join(','), 'Wrong arg');
expect(args[5]).to.equal('--projectdir', 'Wrong arg');
expect(args[6]).to.equal(projectDir, 'Wrong arg');
expect(args[7]).to.equal('--format', 'Wrong arg');
expect(args[8]).to.equal('html', 'Wrong arg');
expect(args[9]).to.equal('--json', 'Wrong arg');
expect(args[3]).to.equal('--projectdir', 'Wrong arg');
expect(args[4]).to.equal(projectDir, 'Wrong arg');
expect(args[5]).to.equal('--format', 'Wrong arg');
expect(args[6]).to.equal('html', 'Wrong arg');
expect(args[7]).to.equal('--json', 'Wrong arg');
expect(args[8]).to.equal('--target', 'Wrong arg');
expect(args[9]).to.equal(targets.join(','), 'Wrong arg');


return args;
}
Expand Down Expand Up @@ -281,6 +282,42 @@ suite('ScanRunner', () => {
Sinon.restore();
});

test('Ignore target when it is empty', () => {
// ===== SETUP =====
Sinon.stub(SettingsManager, 'getGraphEngineDisableWarningViolations').returns(false);
Sinon.stub(SettingsManager, 'getGraphEngineThreadTimeout').returns(null);
Sinon.stub(SettingsManager, 'getGraphEnginePathExpansionLimit').returns(null);
Sinon.stub(SettingsManager, 'getGraphEngineJvmArgs').returns(null);
const emptyTargets = [];

// ===== TEST =====
// Call the test method helper.
const scanner: ScanRunner = new ScanRunner();
const args: string[] = (scanner as any).createDfaArgArray(emptyTargets, projectDir);

// ===== ASSERTIONS =====
// Verify that the right arguments were created.
expect(args).to.not.include('--target', '--target should be ignored when empty');
});

test('Ignore target when it contains only null entries', () => {
// ===== SETUP =====
Sinon.stub(SettingsManager, 'getGraphEngineDisableWarningViolations').returns(false);
Sinon.stub(SettingsManager, 'getGraphEngineThreadTimeout').returns(null);
Sinon.stub(SettingsManager, 'getGraphEnginePathExpansionLimit').returns(null);
Sinon.stub(SettingsManager, 'getGraphEngineJvmArgs').returns(null);
const emptyTargets = [null];

// ===== TEST =====
// Call the test method helper.
const scanner: ScanRunner = new ScanRunner();
const args: string[] = (scanner as any).createDfaArgArray(emptyTargets, projectDir);

// ===== ASSERTIONS =====
// Verify that the right arguments were created.
expect(args).to.not.include('--target', '--target should be ignored when it contains null entry');
});

test('Disable Warning Violations', () => {
// ===== SETUP =====
// Stub the Disable Warning Violations method to return true.
Expand Down

0 comments on commit 243633f

Please sign in to comment.