feat(vscode): add multi-root analysis workflows#978
Conversation
Feature flag enforcement
New feature flags in this PRNone. Violations
|
|
Memory BenchmarksThresholds: bytes/op <= +15.0%, allocs/op <= +10.0%
Result: memory benchmark gate passed. Approval: not required. |
Lopper (Delta)
No dependency-surface deltas detected. |
SonarQube (PR)Open issues: 26 Duplication
IssuesOpen Sonar issues (20)
Source: SonarCloud PR view |
There was a problem hiding this comment.
Pull request overview
This PR significantly expands the VS Code extension surface to support multi-root workspaces and a richer set of analysis workflows (dependency explorer + detail drilldown, baseline save/compare, runtime-trace runs, export formats), and updates the e2e harness with a deterministic CLI fixture.
Changes:
- Add multi-root aware refresh routing plus a new “Lopper” explorer view and dependency detail webview.
- Thread baseline, runtime trace, thresholds/policy settings, single-dependency analysis, and export formats through the runner and command surface.
- Update report typings and e2e fixtures/tests to cover the expanded workflows.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| extensions/vscode-lopper/test-fixtures/lopper-smoke-binary.mjs | Adds deterministic CLI fixture output for extension smoke/e2e flows. |
| extensions/vscode-lopper/src/types.ts | Extends report typings to include baseline/runtime/policy/cache and richer dependency metadata. |
| extensions/vscode-lopper/src/test/suite/smoke.test.ts | Updates smoke test to exercise multi-root behavior and new command surface. |
| extensions/vscode-lopper/src/test/suite/refreshLifecycle.test.ts | Updates harness stubs to match new runner interface (export support). |
| extensions/vscode-lopper/src/test/suite/lopperRunner.test.ts | Adds coverage for runtime/baseline/export flag threading and new executor API. |
| extensions/vscode-lopper/src/test/runTest.mjs | Switches e2e to multi-root workspace launch and uses the deterministic fixture binary. |
| extensions/vscode-lopper/src/lopperRunner.ts | Adds export support and threads runtime/baseline/threshold/policy flags into CLI invocations. |
| extensions/vscode-lopper/src/extension.ts | Adds multi-root controller logic, explorer tree, dependency detail webview, baseline/runtime/export/dependency commands. |
| extensions/vscode-lopper/README.md | Documents new sidebar, multi-root support, settings, and commands. |
| extensions/vscode-lopper/package.json | Contributes new commands, view, and configuration settings; expands activation events. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try { | ||
| await cp(workspaceTemplatePath, workspacePath, { recursive: true }); | ||
| await cp(workspaceTemplatePath, workspacePathTwo, { recursive: true }); | ||
| await rm(path.join(workspacePath, ".lopper-cache"), { recursive: true, force: true }); | ||
| await rm(path.join(workspacePathTwo, ".lopper-cache"), { recursive: true, force: true }); | ||
| await mkdir(path.join(workspacePath, "src"), { recursive: true }); | ||
| await mkdir(path.join(workspacePathTwo, "src"), { recursive: true }); |
| if (command !== "analyse") { | ||
| process.stdout.write(renderExport(command, options, cwd)); | ||
| process.exit(0); | ||
| } | ||
|
|
||
| const dependencyName = options._[0] ?? "scope-lib"; | ||
| const suggestOnly = options["suggest-only"] !== undefined; | ||
| const report = await buildReport(cwd, dependencyName, suggestOnly); | ||
| process.stdout.write(JSON.stringify(report, null, 2)); |
| const stdout = execError.stdout?.trim() ?? ""; | ||
| if (stdout.startsWith("{")) { | ||
| return this.parseReport(stdout, binaryPath); | ||
| if (stdout.length > 0) { | ||
| return stdout; | ||
| } | ||
|
|
| const workspaceFolders = vscode.workspace.workspaceFolders ?? []; | ||
| const primaryFolder = workspaceFolders[0]; | ||
| const secondaryFolder = workspaceFolders[1]; | ||
| const primaryFixtureUri = vscode.Uri.file(path.join(primaryFolder?.uri.fsPath ?? "", "src", "index.ts")); | ||
| const secondaryFixtureUri = vscode.Uri.file(path.join(secondaryFolder?.uri.fsPath ?? "", "src", "index.ts")); |
| const locations = importUse.locations ?? []; | ||
| const firstLocation = locations[0]; | ||
| const item = new LopperExplorerTreeItem( | ||
| importUse.name, | ||
| { | ||
| kind: "import", | ||
| folderPath: folder.uri.fsPath, | ||
| dependencyName, | ||
| filePath: firstLocation ? path.resolve(folder.uri.fsPath, firstLocation.file) : undefined, | ||
| line: firstLocation?.line, | ||
| column: firstLocation?.column, | ||
| }, | ||
| vscode.TreeItemCollapsibleState.None, | ||
| ); | ||
| item.description = importUse.module ?? firstLocation?.file ?? "import"; | ||
| item.tooltip = importUse.locations | ||
| ? `${importUse.name}\n${importUse.locations.map((location) => `${location.file}:${location.line}:${location.column}`).join("\n")}` | ||
| : importUse.name; | ||
| if (firstLocation) { | ||
| item.command = { | ||
| command: "lopper.openLocation", | ||
| title: `Open ${importUse.name}`, | ||
| arguments: [path.resolve(folder.uri.fsPath, firstLocation.file), firstLocation.line, firstLocation.column], | ||
| }; |
| function renderImports(folder: vscode.WorkspaceFolder, imports: LopperImportUse[]): string { | ||
| return `<ul>${imports.map((importUse) => { | ||
| const location = importUse.locations?.[0]; | ||
| const target = location ? commandUri("lopper.openLocation", [path.resolve(folder.uri.fsPath, location.file), location.line, location.column]) : undefined; | ||
| const label = target | ||
| ? `<a href="${target}">${escapeHtml(importUse.name)}</a>` | ||
| : escapeHtml(importUse.name); | ||
| const locationText = importUse.locations?.length | ||
| ? importUse.locations.map((item) => `${item.file}:${item.line}:${item.column}`).join(", ") | ||
| : "n/a"; | ||
| return `<li>${label}${importUse.module ? ` <code>${escapeHtml(importUse.module)}</code>` : ""} <span style="color:#94a3b8">${escapeHtml(locationText)}</span></li>`; | ||
| }).join("")}</ul>`; |



Issue
This bundle covers multi-root workspace support, dependency explorer UX, baseline save/compare workflows, JS/TS runtime trace workflows, threshold and policy controls, single-dependency analysis commands, and export commands for machine-readable outputs.
Closes #495
Closes #496
Closes #609
Closes #610
Closes #611
Closes #612
Closes #613
Cause
The VS Code extension was still centered on a single-folder refresh path and lacked a dedicated sidebar, per-dependency detail workflow, and export/baseline command surface.
Root cause
The extension surface and runner contracts were too narrow: analysis state was tracked as a single workspace flow, report types did not model the richer policy/runtime/baseline data, and the test harness did not exercise multi-root behavior with a deterministic CLI fixture.
Fix
Tests
npm --prefix extensions/vscode-lopper installnpm --prefix extensions/vscode-lopper run compilenpm --prefix extensions/vscode-lopper run test:e2e