Skip to content

feat(vscode): add multi-root analysis workflows#978

Open
ben-ranford wants to merge 1 commit into
mainfrom
feat/issues-495-496-609-610-611-612-613-vscode-workflows
Open

feat(vscode): add multi-root analysis workflows#978
ben-ranford wants to merge 1 commit into
mainfrom
feat/issues-495-496-609-610-611-612-613-vscode-workflows

Conversation

@ben-ranford
Copy link
Copy Markdown
Owner

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

  • Added multi-root workspace tracking, per-folder refresh handling, and active-folder resolution.
  • Added the dependency explorer view and dependency detail webview with drill-down commands.
  • Threaded baseline, runtime trace, threshold, and policy settings through the runner.
  • Added single-dependency analysis and export commands for JSON, CSV, SARIF, and PR-comment output.
  • Expanded report typings to match the richer analysis model.
  • Added a deterministic smoke CLI fixture so the e2e suite validates the real extension workflow without depending on a downloaded binary.

Tests

  • npm --prefix extensions/vscode-lopper install
  • npm --prefix extensions/vscode-lopper run compile
  • npm --prefix extensions/vscode-lopper run test:e2e

Copilot AI review requested due to automatic review settings June 2, 2026 13:45
@ben-ranford ben-ranford added this to the v1.6.0 milestone Jun 2, 2026
@ben-ranford ben-ranford added the enhancement New feature or request label Jun 2, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

Feature flag enforcement

  • Feature PR: yes (feat PR title)
  • Check: failed
  • Rule: feature PRs must add a feature flag, new flags must start as preview, and feature flag ids and names must be unique.

New feature flags in this PR

None.

Violations

  • Feature PRs must add at least one new feature flag in internal/featureflags/features.json.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 2, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

Memory Benchmarks

Thresholds: bytes/op <= +15.0%, allocs/op <= +10.0%

Benchmark Base B/op Head B/op Delta B/op Base allocs/op Head allocs/op Delta allocs/op Status
github.com/ben-ranford/lopper/internal/lang/shared/BenchmarkCountUsage 25632.3 25632.3 +0.0% 375.0 375.0 +0.0% ok
github.com/ben-ranford/lopper/internal/lang/shared/BenchmarkCountUsageRegexPerIdentifier 414127.3 414197.0 +0.0% 3067.0 3067.0 +0.0% ok
github.com/ben-ranford/lopper/internal/report/BenchmarkFormatLargeTable 254744.7 254748.3 +0.0% 3014.0 3014.0 +0.0% ok

Result: memory benchmark gate passed.

Approval: not required.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

Lopper (Delta)

Metric delta Value
Dependency count +0
Used percent +0.0%
Waste percent +0.0%
Estimated unused bytes +0 B
Known licenses +0
Unknown licenses +0
Denied licenses +0
Changed Regressions Progressions Added Removed Unchanged
0 0 0 0 0 9

No dependency-surface deltas detected.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 2, 2026

SonarQube (PR)

Open issues: 26
Actionable issues shown (excluding mock/fixture files): 20

Duplication

  • Overall duplicated lines: 505
  • Overall duplication density: 0.40%
  • New duplicated lines: n/a
  • New duplication density: n/a

Issues

Open Sonar issues (20)
# Severity Rule Location Message
1 MINOR typescript:S1128 extensions/vscode-lopper/src/extension.ts:17 Remove this unused import of 'WorkspaceExportRequest'.
2 MINOR typescript:S1128 extensions/vscode-lopper/src/extension.ts:24 Remove this unused import of 'LopperCacheMetadata'.
3 MINOR typescript:S1128 extensions/vscode-lopper/src/extension.ts:35 Remove this unused import of 'LopperRemovalCandidate'.
4 MINOR typescript:S1128 extensions/vscode-lopper/src/extension.ts:37 Remove this unused import of 'LopperSummaryDelta'.
5 MINOR typescript:S1128 extensions/vscode-lopper/src/extension.ts:38 Remove this unused import of 'LopperSymbolUsage'.
6 MINOR typescript:S1128 extensions/vscode-lopper/src/extension.ts:39 Remove this unused import of 'LopperUsageUncertainty'.
7 CRITICAL typescript:S3776 extensions/vscode-lopper/src/extension.ts:292 Refactor this function to reduce its Cognitive Complexity from 16 to the 15 allowed.
8 MAJOR typescript:S4624 extensions/vscode-lopper/src/extension.ts:1616 Refactor this code to not use nested template literals.
9 CRITICAL typescript:S3776 extensions/vscode-lopper/src/extension.ts:1765 Refactor this function to reduce its Cognitive Complexity from 16 to the 15 allowed.
10 MINOR typescript:S7735 extensions/vscode-lopper/src/extension.ts:1786 Unexpected negated condition.
11 MINOR typescript:S7778 extensions/vscode-lopper/src/extension.ts:1793 Do not call Array#push() multiple times.
12 MINOR typescript:S7778 extensions/vscode-lopper/src/extension.ts:1801 Do not call Array#push() multiple times.
13 MINOR typescript:S7735 extensions/vscode-lopper/src/extension.ts:1808 Unexpected negated condition.
14 MINOR typescript:S7778 extensions/vscode-lopper/src/extension.ts:1813 Do not call Array#push() multiple times.
15 MAJOR typescript:S4624 extensions/vscode-lopper/src/extension.ts:1842 Refactor this code to not use nested template literals.
16 MAJOR typescript:S4624 extensions/vscode-lopper/src/extension.ts:1849 Refactor this code to not use nested template literals.
17 MAJOR typescript:S4624 extensions/vscode-lopper/src/extension.ts:1945 Refactor this code to not use nested template literals.
18 MAJOR typescript:S4624 extensions/vscode-lopper/src/extension.ts:1978 Refactor this code to not use nested template literals.
19 MAJOR typescript:S4624 extensions/vscode-lopper/src/extension.ts:1981 Refactor this code to not use nested template literals.
20 MAJOR typescript:S4624 extensions/vscode-lopper/src/extension.ts:1981 Refactor this code to not use nested template literals.

Source: SonarCloud PR view

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 21 to +27
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 });
Comment on lines +10 to +18
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));
Comment on lines 101 to 105
const stdout = execError.stdout?.trim() ?? "";
if (stdout.startsWith("{")) {
return this.parseReport(stdout, binaryPath);
if (stdout.length > 0) {
return stdout;
}

Comment on lines +9 to +13
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"));
Comment on lines +1600 to +1623
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],
};
Comment on lines +1986 to +1997
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>`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment