Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion esbuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ const watchPlugin = {
console.log("build complete");
}
} catch (err) {
process.stderr.write(err.stderr);
if (err.stderr) {
process.stderr.write(err.stderr);
} else {
console.error(err);
}
process.exit(1);
}
})();
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@
{
"command": "weAudit.boundaryMoveDown",
"title": "weAudit: Move Finding Down"
},
{
"command": "weAudit.editCodeQualityIssueNumber",
"title": "weAudit: Set Code Quality Issue Number"
}
],
"keybindings": [
Expand Down Expand Up @@ -601,6 +605,11 @@
"type": "boolean",
"default": false,
"description": "Sort findings and notes alphabetically by name in the tree view."
},
"weAudit.general.skipCodeQualityConfirmation": {
"type": "boolean",
"default": false,
"description": "Skip the confirmation dialog when opening a Code Quality comment. The comment will be copied to clipboard and the issue opened immediately."
}
}
},
Expand Down
327 changes: 306 additions & 21 deletions src/codeMarker.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/panels/findingDetails.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<span class="detailSpan">Severity:</span>
<vscode-dropdown position="below" id="severity-dropdown">
<vscode-option></vscode-option>
<vscode-option>Code Quality</vscode-option>
<vscode-option>Informational</vscode-option>
<vscode-option>Undetermined</vscode-option>
<vscode-option>Low</vscode-option>
Expand Down
5 changes: 5 additions & 0 deletions src/panels/gitConfig.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@
<span class="detailSpan">Commit Hash:</span>
<vscode-text-field id="commit-hash"></vscode-text-field>
</div>

<div class="detailsDiv">
<span class="detailSpan">Code Quality Issue #:</span>
<vscode-text-field id="cq-issue-number" placeholder="e.g. 42"></vscode-text-field>
</div>
</div>
12 changes: 10 additions & 2 deletions src/panels/gitConfigPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ class GitConfigProvider implements vscode.WebviewViewProvider {

vscode.commands.registerCommand(
"weAudit.setGitConfigView",
(rootPathAndLabel: RootPathAndLabel, clientRepo: string, auditRepo: string, commitHash: string) => {
(rootPathAndLabel: RootPathAndLabel, clientRepo: string, auditRepo: string, commitHash: string, cqIssueNumber?: string) => {
this.currentRootPathAndLabel = rootPathAndLabel;
const msg: UpdateRepositoryMessage = {
command: "update-repository-config",
rootLabel: rootPathAndLabel.rootLabel,
clientURL: clientRepo,
auditURL: auditRepo,
commitHash,
cqIssueNumber: cqIssueNumber ?? "",
};
this._view?.webview.postMessage(msg);
},
Expand Down Expand Up @@ -142,7 +143,14 @@ class GitConfigProvider implements vscode.WebviewViewProvider {
);
return;
}
vscode.commands.executeCommand("weAudit.updateGitConfig", rootPath, message.clientURL, message.auditURL, message.commitHash);
vscode.commands.executeCommand(
"weAudit.updateGitConfig",
rootPath,
message.clientURL,
message.auditURL,
message.commitHash,
message.cqIssueNumber,
);
return;
case "choose-workspace-root":
rootPath = this.dirToPathMap.get(message.rootLabel);
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export enum FindingSeverity {
Low = "Low",
Medium = "Medium",
High = "High",
CodeQuality = "Code Quality",
Undefined = "",
}

Expand Down Expand Up @@ -89,6 +90,8 @@ export interface SerializedData {
// older versions do not have partiallyAuditedFiles
partiallyAuditedFiles?: PartiallyAuditedFile[];
resolvedEntries: Entry[];
// optional code quality issue number for the workspace root
codeQualityIssueNumber?: number;
}

/**
Expand All @@ -104,6 +107,8 @@ export interface FullSerializedData {
// older versions do not have partiallyAuditedFiles
partiallyAuditedFiles?: PartiallyAuditedFile[];
resolvedEntries: FullEntry[];
// optional code quality issue number for the workspace root
codeQualityIssueNumber?: number;
}

/**
Expand Down
24 changes: 23 additions & 1 deletion src/webview/findingDetailsMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ function main(): void {
titleField?.addEventListener("change", handlePersistentFieldChange);

const severityDropdown = document.getElementById("severity-dropdown") as Dropdown;
severityDropdown?.addEventListener("change", handlePersistentFieldChange);
severityDropdown?.addEventListener("change", (e: Event) => {
handlePersistentFieldChange(e);
updateFieldVisibility(severityDropdown.value);
});

const difficultyDropdown = document.getElementById("difficulty-dropdown") as Dropdown;
difficultyDropdown?.addEventListener("change", handlePersistentFieldChange);
Expand Down Expand Up @@ -71,6 +74,7 @@ function main(): void {
descriptionArea.value = message.description;
exploitArea.value = message.exploit;
recommendationArea.value = message.recommendation;
updateFieldVisibility(message.severity as string);
break;

case "hide-finding-details":
Expand Down Expand Up @@ -101,3 +105,21 @@ function handleFieldChange(e: Event, isPersistent: boolean): void {
};
vscode.postMessage(message);
}

const CODE_QUALITY_SEVERITY = "Code Quality";

/**
* Shows or hides detail fields based on whether the finding severity is "Code Quality".
* Code Quality findings only show title, severity, and description.
*/
function updateFieldVisibility(severity: string): void {
const isCodeQuality = severity === CODE_QUALITY_SEVERITY;
const hiddenIds = ["difficulty-dropdown", "type-dropdown", "exploit-area", "recommendation-area"];
for (const id of hiddenIds) {
const element = document.getElementById(id);
const parentDiv = element?.parentElement;
if (parentDiv) {
parentDiv.style.display = isCodeQuality ? "none" : "";
}
}
}
6 changes: 6 additions & 0 deletions src/webview/gitConfigMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ function main(): void {
const commitHash = document.getElementById("commit-hash") as TextField;
commitHash?.addEventListener("change", handleFieldChange);

const cqIssueNumber = document.getElementById("cq-issue-number") as TextField;
cqIssueNumber?.addEventListener("change", handleFieldChange);

// handle the message inside the webview
window.addEventListener("message", (event) => {
const message = event.data;
Expand All @@ -39,6 +42,7 @@ function main(): void {
clientURL.value = message.clientURL;
auditURL.value = message.auditURL;
commitHash.value = message.commitHash;
cqIssueNumber.value = message.cqIssueNumber ?? "";
break;

case "set-workspace-roots":
Expand Down Expand Up @@ -67,6 +71,7 @@ function handleFieldChange(_e: Event): void {
const clientURL = document.getElementById("client-url") as TextField;
const auditURL = document.getElementById("audit-url") as TextField;
const commitHash = document.getElementById("commit-hash") as TextField;
const cqIssueNumber = document.getElementById("cq-issue-number") as TextField;
const rootDropdown = document.getElementById("workspace-root-list-dropdown") as Dropdown;

const message: UpdateRepositoryMessage = {
Expand All @@ -75,6 +80,7 @@ function handleFieldChange(_e: Event): void {
clientURL: clientURL.value,
auditURL: auditURL.value,
commitHash: commitHash.value,
cqIssueNumber: cqIssueNumber.value,
};
vscode.postMessage(message);
}
Expand Down
1 change: 1 addition & 0 deletions src/webview/webviewMessageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface UpdateRepositoryMessage {
clientURL: string;
auditURL: string;
commitHash: string;
cqIssueNumber: string;
}

export interface ChooseWorkspaceRootMessage {
Expand Down
2 changes: 2 additions & 0 deletions test/extension/suite/activation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ suite("Extension Activation", () => {
// Git config commands
"weAudit.editClientRemote",
"weAudit.editAuditRemote",
// Code Quality commands
"weAudit.editCodeQualityIssueNumber",
// Multi-root workspace commands
"weAudit.nextGitConfig",
"weAudit.prevGitConfig",
Expand Down
55 changes: 55 additions & 0 deletions test/fixtures/code-quality.weaudit
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"clientRemote": "https://github.com/client/project",
"gitRemote": "https://github.com/auditor/project-audit",
"gitSha": "abc123def456",
"treeEntries": [
{
"label": "Unused return value",
"entryType": 0,
"author": "alice",
"details": {
"severity": "Code Quality",
"difficulty": "",
"type": "",
"description": "The return value of this function call is not used.",
"exploit": "",
"recommendation": ""
},
"locations": [
{
"path": "src/utils/helpers.ts",
"startLine": 15,
"endLine": 15,
"label": "unused return",
"description": ""
}
]
},
{
"label": "SQL Injection in user input",
"entryType": 0,
"author": "alice",
"details": {
"severity": "High",
"difficulty": "Low",
"type": "Data Validation",
"description": "User input is not properly sanitized.",
"exploit": "An attacker could inject malicious SQL.",
"recommendation": "Use parameterized queries."
},
"locations": [
{
"path": "src/database/queries.ts",
"startLine": 42,
"endLine": 45,
"label": "executeQuery function",
"description": ""
}
]
}
],
"auditedFiles": [],
"partiallyAuditedFiles": [],
"resolvedEntries": [],
"codeQualityIssueNumber": 99
}
87 changes: 87 additions & 0 deletions test/unit/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
entryEquals,
isConfigurationEntry,
isEntry,
isEnumValue,
isLocationEntry,
isPathOrganizerEntry,
isWorkspaceRootEntry,
Expand Down Expand Up @@ -626,4 +627,90 @@ describe("types.ts", () => {
assert.strictEqual(configEntryEquals(a, b), false);
});
});

describe("FindingSeverity.CodeQuality", () => {
it("should have CodeQuality enum value", () => {
assert.strictEqual(FindingSeverity.CodeQuality, "Code Quality");
});

it("should be accepted by isEnumValue", () => {
assert.strictEqual(isEnumValue(FindingSeverity, "Code Quality"), true);
});

it("should reject invalid finding severity values", () => {
assert.strictEqual(isEnumValue(FindingSeverity, "Not A Real Severity"), false);
});

it("should allow creating entry details with CodeQuality severity", () => {
const details = createDefaultEntryDetails();
details.severity = FindingSeverity.CodeQuality;
assert.strictEqual(details.severity, "Code Quality");
});
});

describe("codeQualityIssueNumber in SerializedData", () => {
function createValidEntry(): Entry {
return {
label: "Test Finding",
entryType: EntryType.Finding,
author: "testuser",
details: {
severity: FindingSeverity.CodeQuality,
difficulty: FindingDifficulty.Low,
type: FindingType.Undefined,
description: "Test description",
exploit: "",
recommendation: "",
},
locations: [
{
path: "src/test.ts",
startLine: 1,
endLine: 10,
label: "Location 1",
description: "",
},
],
};
}

it("should validate serialized data with codeQualityIssueNumber", () => {
const data: SerializedData = {
...createDefaultSerializedData(),
treeEntries: [createValidEntry()],
codeQualityIssueNumber: 42,
};
assert.strictEqual(validateSerializedData(data), true);
});

it("should validate serialized data without codeQualityIssueNumber (backwards compatibility)", () => {
const data: SerializedData = createDefaultSerializedData();
assert.strictEqual(data.codeQualityIssueNumber, undefined);
assert.strictEqual(validateSerializedData(data), true);
});

it("should preserve codeQualityIssueNumber through JSON round-trip", () => {
const data: SerializedData = {
...createDefaultSerializedData(),
codeQualityIssueNumber: 123,
};
const json = JSON.stringify(data);
const parsed = JSON.parse(json) as SerializedData;
assert.strictEqual(parsed.codeQualityIssueNumber, 123);
});

it("should have undefined codeQualityIssueNumber when not in JSON", () => {
const json = JSON.stringify({
clientRemote: "",
gitRemote: "",
gitSha: "",
treeEntries: [],
auditedFiles: [],
partiallyAuditedFiles: [],
resolvedEntries: [],
});
const parsed = JSON.parse(json) as SerializedData;
assert.strictEqual(parsed.codeQualityIssueNumber, undefined);
});
});
});
32 changes: 32 additions & 0 deletions test/unit/validators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,4 +461,36 @@ describe("validateSerializedData", () => {
expect(validateSerializedData(data)).to.equal(false);
});
});

describe("Code Quality finding severity", () => {
it("accepts entry with FindingSeverity.CodeQuality", () => {
const data = createDefaultSerializedData();
const entry = createValidEntry();
entry.details.severity = FindingSeverity.CodeQuality;
data.treeEntries = [entry];
expect(validateSerializedData(data)).to.equal(true);
});

it("accepts data with codeQualityIssueNumber field", () => {
const data: any = {
...createValidSerializedData(),
codeQualityIssueNumber: 42,
};
expect(validateSerializedData(data)).to.equal(true);
});

it("accepts data without codeQualityIssueNumber (backwards compatibility)", () => {
const data = createValidSerializedData();
expect(data.codeQualityIssueNumber).to.be.undefined;
expect(validateSerializedData(data)).to.equal(true);
});

it("accepts Code Quality entry in resolvedEntries", () => {
const data = createDefaultSerializedData();
const entry = createValidEntry();
entry.details.severity = FindingSeverity.CodeQuality;
data.resolvedEntries = [entry];
expect(validateSerializedData(data)).to.equal(true);
});
});
});
Loading
Loading