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 (#100)
  • Loading branch information
jag-j committed Jul 8, 2024
1 parent 815e2d0 commit 88f8033
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 22 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
82 changes: 71 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,84 @@ 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) {
if (violationsCacheExists()) {
const choice = await vscode.window.showQuickPick(
['***Yes***', '***No***'],
{
placeHolder: '***We identified a previous Salesforce Graph Engine run. Do you want to only run the previously failed violations from that run?***',
canPickMany: false,
ignoreFocusOut: true
}
);

// Default to "Yes" if no choice is made
const rerunFailedOnly = choice == '***Yes***';
if (rerunFailedOnly) {
// Do nothing for now. This will be implemented as part of W-15639759
return;
} else {
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 {
await runDfaOnWorkspace(context, outputChannel);
}
}

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;
});

// 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, targeting.getProjectDir());
});
}

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

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 +338,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
9 changes: 8 additions & 1 deletion src/lib/targeting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,14 @@ function getNearestMethodSymbol(symbols: GenericSymbol[], cursorPosition: vscode
/**
* Get the project containing the specified file.
*/
export function getProjectDir(targetFile: string): string {
export function getProjectDir(targetFile?: string): string | undefined {
if (!targetFile) {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (workspaceFolders && workspaceFolders.length > 0) {
return workspaceFolders[0].uri.fsPath;
}
return undefined;
}
const uri = vscode.Uri.file(targetFile);
return vscode.workspace.getWorkspaceFolder(uri).uri.fsPath;
}
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 88f8033

Please sign in to comment.