Skip to content

Commit 245f9f1

Browse files
CHANGE(a4d): @W-17821243@: Update our LLM prompt with guided json and turn on 23 rules
1 parent 38c78b7 commit 245f9f1

40 files changed

+1271
-1080
lines changed

jest.config.mjs

+7-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ const config = {
77
collectCoverageFrom: [
88
'src/**/*.ts',
99
],
10+
coveragePathIgnorePatterns: [
11+
'/src/test/',
12+
],
1013
coverageReporters: ['text', 'lcov'],
11-
coverageDirectory: '<rootDir>/coverage/unit'
14+
coverageDirectory: '<rootDir>/coverage/unit',
15+
setupFilesAfterEnv: ['./scripts/setup-jest.ts'],
16+
resetMocks: true
1217
};
1318

14-
export default config;
19+
export default config;

package-lock.json

+24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"esbuild": "^0.25.0",
5656
"eslint": "^9.21.0",
5757
"jest": "^29.7.0",
58+
"jest-mock-vscode": "^4.2.0",
5859
"mocha": "^10.1.0",
5960
"ovsx": "^0.10.1",
6061
"proxyquire": "^2.1.3",

scripts/setup-jest.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Note that the vscode module isn't actually available to be imported inside of jest tests because
2+
// it requires the VS Code window to be running as well. That is, it is not supplied by the node engine,
3+
// but is supplied by the vscode engine. So we must mock out this module here to allow the
4+
// "import * as vscode from 'vscode'" to not complain when running jest tests (which run with the node engine).
5+
6+
import * as jestMockVscode from 'jest-mock-vscode';
7+
8+
function getMockVSCode() {
9+
// Using a 3rd party library to help create the mocks instead of creating them all manually
10+
return jestMockVscode.createVSCodeMock(jest);
11+
}
12+
jest.mock('vscode', getMockVSCode, {virtual: true})

src/extension.ts

+101-52
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,13 @@ import * as vscode from 'vscode';
1010
import {SettingsManager, SettingsManagerImpl} from './lib/settings';
1111
import * as targeting from './lib/targeting'
1212
import {DiagnosticManager, DiagnosticManagerImpl} from './lib/diagnostics';
13-
import {DiffCreateAction} from './lib/actions/diff-create-action';
14-
import {DiffAcceptAction} from './lib/actions/diff-accept-action';
15-
import {DiffRejectAction} from './lib/actions/diff-reject-action';
1613
import {messages} from './lib/messages';
1714
import {Fixer} from './lib/fixer';
1815
import {CoreExtensionService} from './lib/core-extension-service';
1916
import * as Constants from './lib/constants';
2017
import * as path from 'path';
2118
import * as ApexGuruFunctions from './lib/apexguru/apex-guru-service';
22-
import {AgentforceViolationsFixer} from './lib/agentforce/agentforce-violations-fixer'
19+
import {AgentforceViolationFixer} from './lib/agentforce/agentforce-violation-fixer'
2320
import {
2421
CODEGENIE_UNIFIED_DIFF_ACCEPT,
2522
CODEGENIE_UNIFIED_DIFF_ACCEPT_ALL,
@@ -34,6 +31,10 @@ import {TelemetryService} from "./lib/external-services/telemetry-service";
3431
import {DfaRunner} from "./lib/dfa-runner";
3532
import {CodeAnalyzerRunner} from "./lib/code-analyzer-runner";
3633
import {CodeActionProvider, CodeActionProviderMetadata, DocumentSelector} from "vscode";
34+
import {AgentforceCodeActionProvider} from "./lib/agentforce/agentforce-code-action-provider";
35+
import {UnifiedDiffActions} from "./lib/unified-diff/unified-diff-actions";
36+
import {CodeGenieUnifiedDiffTool, UnifiedDiffTool} from "./lib/unified-diff/unified-diff-tool";
37+
import {FixSuggestion} from "./lib/fix-suggestion";
3738

3839

3940
// Object to hold the state of our extension for a specific activation context, to be returned by our activate function
@@ -99,7 +100,13 @@ export async function activate(context: vscode.ExtensionContext): Promise<SFCAEx
99100
if (!vscode.window.activeTextEditor) {
100101
throw new Error(messages.targeting.error.noFileSelected);
101102
}
102-
return codeAnalyzerRunner.runAndDisplay(Constants.COMMAND_RUN_ON_ACTIVE_FILE, [vscode.window.activeTextEditor.document.fileName]);
103+
104+
// Note that the active editor window could be the output window instead of the actual file editor, so we
105+
// force focus it first to ensure we are getting the correct editor
106+
await vscode.commands.executeCommand('workbench.action.focusActiveEditorGroup');
107+
const document: vscode.TextDocument = vscode.window.activeTextEditor.document;
108+
109+
return codeAnalyzerRunner.runAndDisplay(Constants.COMMAND_RUN_ON_ACTIVE_FILE, [document.fileName]);
103110
});
104111
// ... also invoked by opening a file if the user has set things to do so.
105112
onDidOpenTextDocument(async (textDocument: vscode.TextDocument) => {
@@ -178,7 +185,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<SFCAEx
178185
dfaRunner.clearSavedFilesCache();
179186
});
180187

181-
onDidSaveTextDocument((document: vscode.TextDocument) => dfaRunner.addSavedFileToCache(document.uri.fsPath));
188+
onDidSaveTextDocument((document: vscode.TextDocument) => dfaRunner.addSavedFileToCache(document.fileName));
182189

183190

184191
// =================================================================================================================
@@ -219,67 +226,104 @@ export async function activate(context: vscode.ExtensionContext): Promise<SFCAEx
219226

220227

221228
// =================================================================================================================
222-
// == Agentforce for Developers Integration and Unified Diff Functionality
229+
// == Agentforce for Developers Integration
223230
// =================================================================================================================
224-
const agentforceViolationsFixer = new AgentforceViolationsFixer(externalServiceProvider, logger);
225-
registerCodeActionsProvider({pattern: '**/*.cls'}, agentforceViolationsFixer,
231+
const agentforceCodeActionProvider: AgentforceCodeActionProvider = new AgentforceCodeActionProvider(externalServiceProvider, logger);
232+
const agentforceViolationFixer: AgentforceViolationFixer = new AgentforceViolationFixer(externalServiceProvider, logger);
233+
const unifiedDiffTool: UnifiedDiffTool<DiffHunk> = new CodeGenieUnifiedDiffTool();
234+
const unifiedDiffActions: UnifiedDiffActions<DiffHunk> = new UnifiedDiffActions<DiffHunk>(unifiedDiffTool, telemetryService, logger);
235+
236+
registerCodeActionsProvider({language: 'apex'}, agentforceCodeActionProvider,
226237
{providedCodeActionKinds: [vscode.CodeActionKind.QuickFix]});
227238

228-
registerCommand(Constants.UNIFIED_DIFF, async (source: string, code: string, file?: string) => {
229-
await (new DiffCreateAction(`${source}.${Constants.UNIFIED_DIFF}`, {
230-
callback: (code: string, file?: string) => VSCodeUnifiedDiff.singleton.unifiedDiff(code, file),
231-
telemetryService
232-
})).run(code, file);
233-
await VSCodeUnifiedDiff.singleton.unifiedDiff(code, file);
239+
// Invoked by the "quick fix" buttons on A4D enabled diagnostics
240+
registerCommand(Constants.QF_COMMAND_A4D_FIX, async (document: vscode.TextDocument, diagnostic: vscode.Diagnostic) => {
241+
const fixSuggestion: FixSuggestion = await agentforceViolationFixer.suggestFix(document, diagnostic);
242+
if (!fixSuggestion) {
243+
return;
244+
}
245+
246+
diagnosticManager.clearDiagnostic(document.uri, diagnostic);
247+
248+
// TODO: We really need to either improve or replace the CodeGenie unified diff tool. Ideally, we would be
249+
// passing the fixSuggestion to some sort of callback that when the diff is rejected, restore the diagnostic
250+
// that we just removed that is associated with the fix but the CodeGenie diff tool doesn't allow us to do that.
251+
252+
// Display the diff with buttons that call through to the commands:
253+
// CODEGENIE_UNIFIED_DIFF_ACCEPT, CODEGENIE_UNIFIED_DIFF_REJECT, CODEGENIE_UNIFIED_DIFF_ACCEPT_ALL, CODEGENIE_UNIFIED_DIFF_REJECT_ALL
254+
const commandSource: string = Constants.QF_COMMAND_A4D_FIX;
255+
await unifiedDiffActions.createDiff(commandSource, document, fixSuggestion.getFixedDocumentCode());
256+
257+
// We should consider a better way to display the explanation associated with the fix.
258+
if (fixSuggestion.hasExplanation()) {
259+
logger.log(`Explanation of the fix supplied by Agentforce:\n${indent(fixSuggestion.getExplanation())}`);
260+
}
261+
262+
logger.trace(`=== ORIGINAL CODE ===:\n${fixSuggestion.getOriginalCodeToBeFixed()}\n\n` +
263+
`=== FIXED CODE ===:\n${fixSuggestion.getFixedCode()}`);
234264
});
235265

236-
registerCommand(CODEGENIE_UNIFIED_DIFF_ACCEPT, async (hunk: DiffHunk) => {
237-
// TODO: The use of the prefix shouldn't be hardcoded. Ideally, it should be passed in as an argument to the command.
238-
// But that would require us to make changes to the underlying UnifiedDiff code that we're not currently in a position to make.
239-
await (new DiffAcceptAction(`${Constants.A4D_PREFIX}.${CODEGENIE_UNIFIED_DIFF_ACCEPT}`, {
240-
callback: async (diffHunk: DiffHunk) => {
241-
await VSCodeUnifiedDiff.singleton.unifiedDiffAccept(diffHunk);
242-
return diffHunk.lines.length;
243-
},
244-
telemetryService
245-
})).run(hunk);
246-
// For accept & accept all, it is tricky to track the diagnostics and the changed lines as multiple fixes are requested.
247-
// Hence, we save the file and rerun the scan instead.
248-
await vscode.window.activeTextEditor.document.save();
249-
return codeAnalyzerRunner.runAndDisplay(Constants.COMMAND_RUN_ON_ACTIVE_FILE, [vscode.window.activeTextEditor.document.fileName]);
266+
267+
// =================================================================================================================
268+
// == CodeGenie Unified Diff Integration
269+
// =================================================================================================================
270+
271+
VSCodeUnifiedDiff.singleton.activate(context);
272+
273+
// Invoked by the "Accept" button on the CodeGenie Unified Diff tool
274+
registerCommand(CODEGENIE_UNIFIED_DIFF_ACCEPT, async (diffHunk: DiffHunk) => {
275+
// Unfortunately the CodeGenie diff tool doesn't pass in the original source of the diff, so we hardcode
276+
// this for now since A4D is the only source for the unified diff so far.
277+
const commandSource: string = `${Constants.QF_COMMAND_A4D_FIX}>${CODEGENIE_UNIFIED_DIFF_ACCEPT}`;
278+
// Also, CodeGenie diff tool does not pass in the document, and so we assume it is the active one since the user clicked the button.
279+
const document: vscode.TextDocument = vscode.window.activeTextEditor.document;
280+
281+
await unifiedDiffActions.acceptDiffHunk(commandSource, document, diffHunk);
250282
});
251283

252-
registerCommand(CODEGENIE_UNIFIED_DIFF_REJECT, async (hunk: DiffHunk) => {
253-
// TODO: The use of the prefix shouldn't be hardcoded. Ideally, it should be passed in as an argument to the command.
254-
// But that would require us to make changes to the underlying UnifiedDiff code that we're not currently in a position to make.
255-
await (new DiffRejectAction(`${Constants.A4D_PREFIX}.${CODEGENIE_UNIFIED_DIFF_REJECT}`, {
256-
callback: (diffHunk: DiffHunk) => VSCodeUnifiedDiff.singleton.unifiedDiffReject(diffHunk),
257-
telemetryService
258-
})).run(hunk);
284+
// Invoked by the "Reject" button on the CodeGenie Unified Diff tool
285+
registerCommand(CODEGENIE_UNIFIED_DIFF_REJECT, async (diffHunk: DiffHunk) => {
286+
// Unfortunately the CodeGenie diff tool doesn't pass in the original source of the diff, so we hardcode
287+
// this for now since A4D is the only source for the unified diff so far.
288+
const commandSource: string = `${Constants.QF_COMMAND_A4D_FIX}>${CODEGENIE_UNIFIED_DIFF_REJECT}`;
289+
// Also, CodeGenie diff tool does not pass in the document, and so we assume it is the active one since the user clicked the button.
290+
const document: vscode.TextDocument = vscode.window.activeTextEditor.document;
291+
await unifiedDiffActions.rejectDiffHunk(commandSource, document, diffHunk);
292+
293+
// Work Around: For reject & reject all, we really should be restoring the diagnostic that we removed
294+
// but CodeGenie doesn't let us keep the diagnostic information around at this point. So instead we must
295+
// rerun the scan instead to get the diagnostic restored.
296+
await document.save(); // TODO: saving the document should be built in to the runAndDisplay command in my opinion
297+
return codeAnalyzerRunner.runAndDisplay(Constants.COMMAND_RUN_ON_ACTIVE_FILE, [document.fileName]);
259298
});
260299

300+
// Invoked by the "Accept All" button on the CodeGenie Unified Diff tool
261301
registerCommand(CODEGENIE_UNIFIED_DIFF_ACCEPT_ALL, async () => {
262-
// TODO: The use of the prefix shouldn't be hardcoded. Ideally, it should be passed in as an argument to the command.
263-
// But that would require us to make changes to the underlying UnifiedDiff code that we're not currently in a position to make.
264-
await (new DiffAcceptAction(`${Constants.A4D_PREFIX}.${CODEGENIE_UNIFIED_DIFF_ACCEPT_ALL}`, {
265-
callback: () => VSCodeUnifiedDiff.singleton.unifiedDiffAcceptAll(),
266-
telemetryService
267-
})).run();
268-
await vscode.window.activeTextEditor.document.save();
269-
return codeAnalyzerRunner.runAndDisplay(Constants.COMMAND_RUN_ON_ACTIVE_FILE, [vscode.window.activeTextEditor.document.fileName]);
302+
// Unfortunately the CodeGenie diff tool doesn't pass in the original source of the diff, so we hardcode
303+
// this for now since A4D is the only source for the unified diff so far.
304+
const commandSource: string = `${Constants.QF_COMMAND_A4D_FIX}>${CODEGENIE_UNIFIED_DIFF_ACCEPT_ALL}`;
305+
// Also, CodeGenie diff tool does not pass in the document, and so we assume it is the active one since the user clicked the button.
306+
const document: vscode.TextDocument = vscode.window.activeTextEditor.document;
307+
308+
await unifiedDiffActions.acceptAll(commandSource, document);
270309
});
271310

311+
// Invoked by the "Reject All" button on the CodeGenie Unified Diff tool
272312
registerCommand(CODEGENIE_UNIFIED_DIFF_REJECT_ALL, async () => {
273-
// TODO: The use of the prefix shouldn't be hardcoded. Ideally, it should be passed in as an argument to the command.
274-
// But that would require us to make changes to the underlying UnifiedDiff code that we're not currently in a position to make.
275-
await (new DiffRejectAction(`${Constants.A4D_PREFIX}.${CODEGENIE_UNIFIED_DIFF_REJECT_ALL}`, {
276-
callback: () => VSCodeUnifiedDiff.singleton.unifiedDiffRejectAll(),
277-
telemetryService
278-
})).run();
313+
// Unfortunately the CodeGenie diff tool doesn't pass in the original source of the diff, so we hardcode
314+
// this for now since A4D is the only source for the unified diff so far.
315+
const commandSource: string = `${Constants.QF_COMMAND_A4D_FIX}>${CODEGENIE_UNIFIED_DIFF_REJECT_ALL}`;
316+
// Also, CodeGenie diff tool does not pass in the document, and so we assume it is the active one since the user clicked the button.
317+
const document: vscode.TextDocument = vscode.window.activeTextEditor.document;
318+
await unifiedDiffActions.rejectAll(commandSource, document);
319+
320+
// Work Around: For reject & reject all, we really should be restoring the diagnostic that we removed
321+
// but CodeGenie doesn't let us keep the diagnostic information around at this point. So instead we must
322+
// rerun the scan instead to get the diagnostic restored.
323+
await document.save(); // TODO: saving the document should be built in to the runAndDisplay command in my opinion
324+
return codeAnalyzerRunner.runAndDisplay(Constants.COMMAND_RUN_ON_ACTIVE_FILE, [document.fileName]);
279325
});
280326

281-
VSCodeUnifiedDiff.singleton.activate(context);
282-
283327

284328
// =================================================================================================================
285329
// == Finalize activation
@@ -317,3 +361,8 @@ async function establishVariableInContext(varUsedInPackageJson: string, getValue
317361
await vscode.commands.executeCommand('setContext', varUsedInPackageJson, await getValueFcn());
318362
});
319363
}
364+
365+
export function indent(value: string, indentation = ' '): string {
366+
return indentation + value.replace(/\n/g, `\n${indentation}`);
367+
}
368+

src/lib/actions/diff-accept-action.ts

-40
This file was deleted.

0 commit comments

Comments
 (0)