diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dc374d..9bfe702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 0.3.1 + +- Generate references report (beta). +- Bugfixes. + ## 0.3.0 - Improve the view of imported notes. @@ -14,7 +19,7 @@ - Added an explorer to track the progress of the audit. - Possibility to mark files as reviewed. -- Functionality to exclude files and folders from the audit. +- Functionality to exclude files and folders from the audit scope. - Cleaning of icons to reduce the size of the extension. - Minify SVG. - Fix: update minimist package. diff --git a/package.json b/package.json index 2e4588b..98245b2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "code-auditor", "displayName": "CodeAuditor", "description": "Code Auditor Notebook", - "version": "0.3.0", + "version": "0.3.1", "publisher": "red4sec", "icon": "resources/code-auditor.png", "readme": "README.md", @@ -40,6 +40,7 @@ "onCommand:code-auditor.newNote", "onCommand:code-auditor.removeNote", "onCommand:code-auditor.generateReport", + "onCommand:code-auditor.generateReferences", "onCommand:code-auditor.setNoteState", "onCommand:code-auditor.importSlither", "onCommand:code-auditor.importSemgrep", @@ -113,6 +114,14 @@ "dark": "resources/dark/preview.svg" } }, + { + "command": "code-auditor.generateReferences", + "title": "Audit: Generate references", + "icon": { + "light": "resources/light/preview.svg", + "dark": "resources/dark/preview.svg" + } + }, { "command": "code-auditor.importSlither", "title": "Audit: Import Slither", @@ -264,6 +273,10 @@ "command": "code-auditor.generateReport", "when": "view == code-auditor.noteExplorer" }, + { + "command": "code-auditor.generateReferences", + "when": "view == code-auditor.noteExplorer" + }, { "command": "code-auditor.importSlither", "when": "view == code-auditor.noteExplorer" @@ -272,7 +285,6 @@ "command": "code-auditor.importSemgrep", "when": "view == code-auditor.noteExplorer" } - ], "view/title": [ { diff --git a/src/codeauditor.ts b/src/codeauditor.ts index ca66d2a..5698d7f 100644 --- a/src/codeauditor.ts +++ b/src/codeauditor.ts @@ -5,7 +5,7 @@ import { noteProvider } from './noteexplorer'; import { fileState, noteState, noteType } from './types'; import { excludePath, newNote, removeNote, setFileState, setNoteState, setNoteType } from './notes'; import { progressProvider } from './progressexplorer'; -import { generateReport } from './report'; +import { generateReferences, generateReport } from './report'; import { auditDataInit } from './storage'; import { ImportSemgrepReport, ImportSlitherReport } from './importnotes'; @@ -49,10 +49,15 @@ export function activate(context: vscode.ExtensionContext) { if (item) { excludePath(item.uri); } })); context.subscriptions.push(vscode.commands.registerCommand('code-auditor.generateReport', () => { - const out = vscode.window.createOutputChannel("report"); + const out = vscode.window.createOutputChannel("Audit Report"); generateReport(out); out.show(); })); + context.subscriptions.push(vscode.commands.registerCommand('code-auditor.generateReferences', () => { + const out = vscode.window.createOutputChannel("Audit References"); + generateReferences(out); + out.show(); + })); context.subscriptions.push(vscode.commands.registerCommand('code-auditor.importSlither', () => { ImportSlitherReport(); })); diff --git a/src/filter.ts b/src/filter.ts index 7a51778..9e14a44 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -17,7 +17,7 @@ export const currentFilter: filerOptions = { }; export function toggleFilter(filter: string) { - if(Object.prototype.hasOwnProperty.call(currentFilter, filter)) { + if (Object.prototype.hasOwnProperty.call(currentFilter, filter)) { currentFilter[filter] = !currentFilter[filter]; } vscode.commands.executeCommand('setContext', 'code-auditor.filter.' + filter, currentFilter[filter]); @@ -60,6 +60,9 @@ export function listFilterNotes(): FileCollection { } notes[parseInt(lineNum)] = note; } + if (Object.keys(notes).length === 0) { + continue; + } nodes[fileName] = { lines: fileInfo.lines, state: fileInfo.state, diff --git a/src/noteexplorer.ts b/src/noteexplorer.ts index 9735eae..e4eba86 100644 --- a/src/noteexplorer.ts +++ b/src/noteexplorer.ts @@ -46,9 +46,6 @@ export class noteProvider implements vscode.TreeDataProvider { const nodes: noteNode[] = []; for (const [fileName, fileInfo] of Object.entries(filteredNotes)) { - if (Object.keys(fileInfo.notes).length === 0) { - continue; - } const label = path.parse(fileName).base; const desc = path.parse(fileName).dir; nodes.push( diff --git a/src/report.ts b/src/report.ts index 8bc78cf..5109431 100644 --- a/src/report.ts +++ b/src/report.ts @@ -1,6 +1,8 @@ import * as vscode from 'vscode'; +import { URL } from 'url'; import { auditData } from './storage'; import { listFilterNotes } from './filter'; +import { noteSeparator } from './importnotes'; export function generateReport(out: vscode.OutputChannel) { @@ -8,15 +10,88 @@ export function generateReport(out: vscode.OutputChannel) { vscode.window.showErrorMessage("Extension not ready"); return; } - for (const [fileName, fileInfo] of Object.entries(listFilterNotes())) { - if (Object.keys(fileInfo.notes).length === 0) { - continue; - } + + const filteredNotes = listFilterNotes(); + for (const fileName of Object.keys(filteredNotes).sort()) { out.appendLine(`${fileName}`); - for (const [lineNum, note] of Object.entries(fileInfo.notes)) { + for (const [lineNum, note] of Object.entries(filteredNotes[fileName].notes)) { let afectedLines = note.length > 1 ? `${lineNum}:${parseInt(lineNum) + note.length - 1}` : lineNum; if (afectedLines.length < 4) { afectedLines += '\t'; } out.appendLine(`\t${afectedLines}\t- ${note.type}: ${note.state}\t=> ${note.message}`); } } } + +export async function generateReferences(out: vscode.OutputChannel) { + if (!auditData) { + vscode.window.showErrorMessage("Extension not ready"); + return; + } + + const inputSort = await vscode.window.showInputBox({ + value: "3", + prompt: "Sort by the first # words", + placeHolder: "number between 1 and 10", + ignoreFocusOut: true, + }); + if (!inputSort) { + vscode.window.showErrorMessage("Operation cancelled"); + return; + } + let sortBy = parseInt(inputSort); + if (!sortBy || sortBy < 1 || sortBy > 10) { sortBy = 2; } + + let inputUrl = await vscode.window.showInputBox({ + prompt: "Enter the base URL or leave empty", + placeHolder: "optional base URL", + ignoreFocusOut: true + }); + let baseURL; + if (inputUrl) { + inputUrl = inputUrl.trim(); + if (!inputUrl.endsWith('/')) { + inputUrl += '/'; + } + try { + const tryURL = new URL(inputUrl); + baseURL = inputUrl; + } catch (error) { + vscode.window.showInformationMessage("Invalid base URL"); + baseURL = ""; + } + } + + const noterefs: { [key: string]: string[]; } = {}; + const filteredNotes = listFilterNotes(); + for (const fileName of Object.keys(filteredNotes).sort()) { + for (const [lineNum, note] of Object.entries(filteredNotes[fileName].notes)) { + if (!note.message) { continue; } + const lineInd = baseURL ? 'L' : ''; + const afectedLines = note.length > 1 ? `${lineInd}${lineNum}-${lineInd}${parseInt(lineNum) + note.length - 1}` : lineInd + lineNum; + + for (const x of note.message.split(noteSeparator)) { + const key = x.toLowerCase().split(' ').slice(0, sortBy).join(' '); + const ref = `${fileName}#${afectedLines}`; + + if (noterefs[key]) { + if (!noterefs[key].includes(ref)) { + noterefs[key].push(ref); + } + } else { + noterefs[key] = [ref]; + } + } + } + } + + for (const desc of Object.keys(noterefs).sort()) { + out.appendLine(`${desc}`); + for (const ref of Object.values(noterefs[desc])) { + if (baseURL) { + out.appendLine(`\t ${new URL(baseURL + ref)}`); + } else { + out.appendLine(`\t- ${ref}`); + } + } + } +}