diff --git a/src/ui/codeActionProvider.ts b/src/ui/codeActionProvider.ts new file mode 100644 index 0000000..4e32c88 --- /dev/null +++ b/src/ui/codeActionProvider.ts @@ -0,0 +1,113 @@ +import * as vscode from 'vscode'; + +export class ProofCodeCodeActionProvider implements vscode.CodeActionProvider { + public static readonly providedCodeActionKinds = [vscode.CodeActionKind.QuickFix]; + + public provideCodeActions( + document: vscode.TextDocument, + _range: vscode.Range | vscode.Selection, + context: vscode.CodeActionContext, + _token: vscode.CancellationToken + ): vscode.CodeAction[] { + const actions: vscode.CodeAction[] = []; + + // Filter diagnostics generated by ProofCode + const proofCodeDiagnostics = context.diagnostics.filter( + (diag) => diag.source === 'ProofCode' + ); + + for (const diag of proofCodeDiagnostics) { + const lineIdx = diag.range.start.line; + const line = document.lineAt(lineIdx); + const lineText = line.text; + const indentMatch = lineText.match(/^\s*/); + const indent = indentMatch ? indentMatch[0] : ''; + + // 1. Quick Fix for PC-REL-006: Unhandled Async Operation + if (diag.code === 'PC-REL-006') { + const action = new vscode.CodeAction( + '🛡️ ProofCode: Wrap in try/catch block', + vscode.CodeActionKind.QuickFix + ); + action.isPreferred = true; + action.diagnostics = [diag]; + + const edit = new vscode.WorkspaceEdit(); + const wrapped = `${indent}try {\n${indent} ${lineText.trim()}\n${indent}} catch (error) {\n${indent} console.error('Async operation failed:', error);\n${indent}}`; + edit.replace(document.uri, line.range, wrapped); + action.edit = edit; + actions.push(action); + } + + // 2. Quick Fix for PC-DATA-001: Unhandled Null/Undefined Database Result + if (diag.code === 'PC-DATA-001') { + // Match variable name from diagnostic message or code line + const varMatch = diag.message.match(/\(([^)]+)\)/); + const varName = varMatch ? varMatch[1] : 'record'; + + // Fix A: Add null guard + const guardAction = new vscode.CodeAction( + `🛡️ ProofCode: Add null guard check (if (!${varName}))`, + vscode.CodeActionKind.QuickFix + ); + guardAction.isPreferred = true; + guardAction.diagnostics = [diag]; + + const guardEdit = new vscode.WorkspaceEdit(); + const guardText = `${indent}if (!${varName}) throw new Error('${varName} not found');\n`; + guardEdit.insert(document.uri, new vscode.Position(lineIdx, 0), guardText); + guardAction.edit = guardEdit; + actions.push(guardAction); + + // Fix B: Use optional chaining + const dotIndex = lineText.indexOf(`${varName}.`); + if (dotIndex !== -1) { + const optChainAction = new vscode.CodeAction( + `🛡️ ProofCode: Use optional chaining (${varName}?.${lineText.slice(dotIndex + varName.length + 1).split(/\W/)[0]})`, + vscode.CodeActionKind.QuickFix + ); + optChainAction.diagnostics = [diag]; + + const optEdit = new vscode.WorkspaceEdit(); + const dotRange = new vscode.Range( + lineIdx, + dotIndex + varName.length, + lineIdx, + dotIndex + varName.length + 1 + ); + optEdit.replace(document.uri, dotRange, '?.'); + optChainAction.edit = optEdit; + actions.push(optChainAction); + } + } + + // 3. Quick Fix for PC-AUTH-001: Missing Authorization Check in API Route + if (diag.code === 'PC-AUTH-001') { + const authAction = new vscode.CodeAction( + '🛡️ ProofCode: Insert session authentication guard', + vscode.CodeActionKind.QuickFix + ); + authAction.isPreferred = true; + authAction.diagnostics = [diag]; + + const authEdit = new vscode.WorkspaceEdit(); + const authSnippet = `${indent} const session = await auth();\n${indent} if (!session) return new Response('Unauthorized', { status: 401 });\n`; + authEdit.insert(document.uri, new vscode.Position(lineIdx + 1, 0), authSnippet); + authAction.edit = authEdit; + actions.push(authAction); + } + + // 4. Quick Fix for PC-SEC-001: Potential SQL Injection + if (diag.code === 'PC-SEC-001') { + const sqlAction = new vscode.CodeAction( + '🛡️ ProofCode: Review query parameterization guidance', + vscode.CodeActionKind.QuickFix + ); + sqlAction.diagnostics = [diag]; + actions.push(sqlAction); + } + } + + return actions; + } +}