From 8f8b9ff5c44f0e3e5439301dee5fdaa13f6b21a9 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 17 Feb 2026 10:16:01 -0800 Subject: [PATCH 1/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rename=20endpoint=20to?= =?UTF-8?q?=20path=20operation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- TELEMETRY.md | 4 +- .../{endpoints.gif => path-operations.gif} | Bin package.json | 56 +++++++-------- src/core/index.ts | 2 +- src/core/pathUtils.ts | 34 ++++----- src/core/types.ts | 2 +- src/extension.ts | 47 ++++++------ src/test/core/pathUtils.test.ts | 68 ++++++++++-------- ...dpointData.ts => mockPathOperationData.ts} | 4 +- .../providers/endpointTreeProvider.test.ts | 33 +++++---- .../providers/testCodeLensProvider.test.ts | 2 +- ...ovider.ts => pathOperationTreeProvider.ts} | 27 +++---- src/vscode/testCodeLensProvider.ts | 4 +- 14 files changed, 154 insertions(+), 135 deletions(-) rename media/walkthrough/{endpoints.gif => path-operations.gif} (100%) rename src/test/fixtures/{mockEndpointData.ts => mockPathOperationData.ts} (98%) rename src/vscode/{endpointTreeProvider.ts => pathOperationTreeProvider.ts} (91%) diff --git a/README.md b/README.md index 53b748c..900c164 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ A Visual Studio Code extension for FastAPI application development. Available on This extension enhances the FastAPI development experience in Visual Studio Code by providing: -### Endpoint Explorer +### Path Operation Explorer -The Endpoint Explorer provides a hierarchical tree view of all FastAPI routes in your application. You can expand routers to see their associated endpoints, and click on any route to jump directly to its definition in the code. You can also jump to router definitions by right-clicking on a router node. +The Path Operation Explorer provides a hierarchical tree view of all FastAPI routes in your application. You can expand routers to see their associated path operations, and click on any route to jump directly to its definition in the code. You can also jump to router definitions by right-clicking on a router node. -![Endpoint Explorer GIF](media/walkthrough/endpoints.gif) +![Path Operation Explorer GIF](media/walkthrough/path-operations.gif) ### Search for routes diff --git a/TELEMETRY.md b/TELEMETRY.md index 293034d..5dbdd27 100644 --- a/TELEMETRY.md +++ b/TELEMETRY.md @@ -28,7 +28,7 @@ This disables only the FastAPI extension's telemetry while leaving other telemet We collect anonymous usage metrics to improve the extension. We do **not** collect: - File paths or file contents -- Route paths or endpoint names +- Route paths or path operation names - Any code from your project - IP addresses (geo-IP is disabled) @@ -42,7 +42,7 @@ We collect anonymous usage metrics to improve the extension. We do **not** colle | Extension deactivated | Session duration (time from activation to deactivation) | Helps us understand how long users keep VS Code open with the extension active | | Activation failed | Error category (e.g., "parse_error", "wasm_load_error"), failure stage | Helps us debug issues users encounter | | Entrypoint detected | Detection duration, method used (config/pyproject/heuristic), success/failure, routes and routers count | Helps us understand which detection methods work best | -| Tree view visible | _(none)_ | Know if users see the endpoint explorer | +| Tree view visible | _(none)_ | Know if users see the path operation explorer | | Search executed | Number of results, whether user selected a result | Helps us understand search usage | | CodeLens provided | Number of test calls found, number matched to routes | Helps us understand CodeLens effectiveness | | Routes navigated | Count of navigations (cumulative) | Helps us understand feature usage depth | diff --git a/media/walkthrough/endpoints.gif b/media/walkthrough/path-operations.gif similarity index 100% rename from media/walkthrough/endpoints.gif rename to media/walkthrough/path-operations.gif diff --git a/package.json b/package.json index e246602..b04be71 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,8 @@ ], "commands": [ { - "command": "fastapi-vscode.refreshEndpoints", - "title": "Refresh Endpoints", + "command": "fastapi-vscode.refreshPathOperations", + "title": "Refresh Path Operations", "category": "FastAPI", "icon": "$(refresh)" }, @@ -52,8 +52,8 @@ "icon": "$(fold)" }, { - "command": "fastapi-vscode.copyEndpointPath", - "title": "Copy Endpoint Path", + "command": "fastapi-vscode.copyPathOperationPath", + "title": "Copy Path to Path Operation", "category": "FastAPI" }, { @@ -67,8 +67,8 @@ "category": "FastAPI" }, { - "command": "fastapi-vscode.searchEndpoints", - "title": "Search Endpoints...", + "command": "fastapi-vscode.searchPathOperations", + "title": "Search Path Operations...", "category": "FastAPI", "icon": "$(search)" }, @@ -105,7 +105,7 @@ ], "keybindings": [ { - "command": "fastapi-vscode.searchEndpoints", + "command": "fastapi-vscode.searchPathOperations", "key": "ctrl+shift+e", "mac": "cmd+shift+e" } @@ -113,7 +113,7 @@ "menus": { "commandPalette": [ { - "command": "fastapi-vscode.copyEndpointPath", + "command": "fastapi-vscode.copyPathOperationPath", "when": "false" }, { @@ -147,25 +147,25 @@ ], "view/title": [ { - "command": "fastapi-vscode.refreshEndpoints", - "when": "view == endpoint-explorer", + "command": "fastapi-vscode.refreshPathOperations", + "when": "view == path-operation-explorer", "group": "navigation@1" }, { "command": "fastapi-vscode.toggleRouters", - "when": "view == endpoint-explorer", + "when": "view == path-operation-explorer", "group": "navigation@2" } ], "view/item/context": [ { - "command": "fastapi-vscode.copyEndpointPath", - "when": "view == endpoint-explorer && viewItem == route", + "command": "fastapi-vscode.copyPathOperationPath", + "when": "view == path-operation-explorer && viewItem == route", "group": "navigation" }, { "command": "fastapi-vscode.goToRouter", - "when": "view == endpoint-explorer && viewItem == router", + "when": "view == path-operation-explorer && viewItem == router", "group": "navigation" } ] @@ -189,8 +189,8 @@ "views": { "fastapi": [ { - "id": "endpoint-explorer", - "name": "Endpoint Explorer", + "id": "path-operation-explorer", + "name": "Path Operation Explorer", "icon": "media/icons/logo-outline.svg" } ], @@ -205,7 +205,7 @@ }, "viewsWelcome": [ { - "view": "endpoint-explorer", + "view": "path-operation-explorer", "contents": "No FastAPI apps found in this workspace.\n\n[Configure Entry Point](command:workbench.action.openSettings?[\"fastapi.entryPoint\"])\n\n[Open Getting Started](command:workbench.action.openWalkthrough?{\"category\":\"FastAPILabs.fastapi-vscode#fastapi-getting-started\"})" } ], @@ -217,27 +217,27 @@ "icon": "media/icons/logo-teal.svg", "steps": [ { - "id": "view-endpoints", - "title": "View Your API Endpoints", - "description": "Open the Endpoint Explorer in the Activity Bar (⚡) to see all routes in your FastAPI application.\n[Open Endpoint Explorer](command:endpoint-explorer.focus)", + "id": "view-path-operations", + "title": "View Your Path Operations", + "description": "Open the Path Operation Explorer in the Activity Bar (⚡) to see all routes in your FastAPI application.\n[Open Path Operation Explorer](command:path-operation-explorer.focus)", "media": { - "image": "media/walkthrough/endpoints.gif", - "altText": "Endpoint Explorer showing FastAPI routes organized by router" + "image": "media/walkthrough/path-operations.gif", + "altText": "Path Operation Explorer showing FastAPI routes organized by router" }, "completionEvents": [ - "onView:endpoint-explorer" + "onView:path-operation-explorer" ] }, { - "id": "search-endpoints", - "title": "Quickly Find Endpoints", - "description": "Instantly filter and navigate to any endpoint in your application.\n[Search Endpoints](command:fastapi-vscode.searchEndpoints)", + "id": "search-path-operations", + "title": "Quickly Find Path Operations", + "description": "Instantly filter and navigate to any path operation in your application.\n[Search Path Operations](command:fastapi-vscode.searchPathOperations)", "media": { "image": "media/walkthrough/search.gif", - "altText": "Search endpoints quick pick showing filtered results" + "altText": "Search path operations quick pick showing filtered results" }, "completionEvents": [ - "onCommand:fastapi-vscode.searchEndpoints" + "onCommand:fastapi-vscode.searchPathOperations" ] }, { diff --git a/src/core/index.ts b/src/core/index.ts index 7d1f912..920caaa 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,5 +1,5 @@ /** - * Public API for FastAPI endpoint discovery. + * Public API for FastAPI path operation discovery. * This module can be used independently of VSCode. */ diff --git a/src/core/pathUtils.ts b/src/core/pathUtils.ts index ba1f4fb..e139ba6 100644 --- a/src/core/pathUtils.ts +++ b/src/core/pathUtils.ts @@ -89,7 +89,7 @@ export function countSegments(path: string): number { } /** - * Checks if a test path matches an endpoint path pattern. + * Checks if a test path matches a path operation path pattern. * Both paths may contain dynamic segments like {item_id} or {settings.API_V1_STR} * which match any segment. * @@ -97,17 +97,17 @@ export function countSegments(path: string): number { * before comparison. * * Examples: - * pathMatchesEndpoint("/items/123", "/items/{item_id}") -> true - * pathMatchesEndpoint("/items/123/details", "/items/{item_id}") -> false - * pathMatchesEndpoint("/users/abc/posts/456", "/users/{user_id}/posts/{post_id}") -> true - * pathMatchesEndpoint("/items/", "/items/{item_id}") -> false - * pathMatchesEndpoint("{settings.API}/apps/{id}", "/apps/{app_id}") -> true - * pathMatchesEndpoint("{BASE}/users/{id}", "/users/{user_id}") -> true - * pathMatchesEndpoint("/teams/?owner=true", "/teams") -> true (query string stripped) + * pathMatchesPathOperation("/items/123", "/items/{item_id}") -> true + * pathMatchesPathOperation("/items/123/details", "/items/{item_id}") -> false + * pathMatchesPathOperation("/users/abc/posts/456", "/users/{user_id}/posts/{post_id}") -> true + * pathMatchesPathOperation("/items/", "/items/{item_id}") -> false + * pathMatchesPathOperation("{settings.API}/apps/{id}", "/apps/{app_id}") -> true + * pathMatchesPathOperation("{BASE}/users/{id}", "/users/{user_id}") -> true + * pathMatchesPathOperation("/teams/?owner=true", "/teams") -> true (query string stripped) */ -export function pathMatchesEndpoint( +export function pathMatchesPathOperation( testPath: string, - endpointPath: string, + pathOperationPath: string, ): boolean { // Strip query string from test path (e.g., "/teams/?owner=true" -> "/teams/") const testPathWithoutQuery = testPath.split("?")[0] @@ -116,26 +116,26 @@ export function pathMatchesEndpoint( const testSegments = stripLeadingDynamicSegments(testPathWithoutQuery) .split("/") .filter(Boolean) - const endpointSegments = stripLeadingDynamicSegments(endpointPath) + const pathOperationSegments = stripLeadingDynamicSegments(pathOperationPath) .split("/") .filter(Boolean) // Segment counts must match - if (testSegments.length !== endpointSegments.length) { + if (testSegments.length !== pathOperationSegments.length) { return false } // Compare each segment positionally return testSegments.every((testSeg, i) => { - const endpointSeg = endpointSegments[i] + const pathOperationSeg = pathOperationSegments[i] // Dynamic segments (e.g., {id}, {app.id}) match any value const testIsDynamic = testSeg.startsWith("{") && testSeg.endsWith("}") - const endpointIsDynamic = - endpointSeg.startsWith("{") && endpointSeg.endsWith("}") - if (testIsDynamic || endpointIsDynamic) { + const pathOperationIsDynamic = + pathOperationSeg.startsWith("{") && pathOperationSeg.endsWith("}") + if (testIsDynamic || pathOperationIsDynamic) { return true } - return testSeg === endpointSeg + return testSeg === pathOperationSeg }) } diff --git a/src/core/types.ts b/src/core/types.ts index b669e83..56cfddb 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -1,5 +1,5 @@ /** - * Public API types for FastAPI endpoint discovery. + * Public API types for FastAPI path operation discovery. */ export type HTTPMethod = diff --git a/src/extension.ts b/src/extension.ts index 3fa2ca4..1731f91 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,5 @@ /** - * VSCode extension entry point for FastAPI endpoint discovery. + * VSCode extension entry point for FastAPI path operation discovery. */ import * as vscode from "vscode" @@ -32,10 +32,10 @@ import { trackTreeViewVisible, } from "./utils/telemetry" import { - type EndpointTreeItem, - EndpointTreeProvider, METHOD_ICONS, -} from "./vscode/endpointTreeProvider" + type PathOperationTreeItem, + PathOperationTreeProvider, +} from "./vscode/pathOperationTreeProvider" import { TestCodeLensProvider } from "./vscode/testCodeLensProvider" export const EXTENSION_ID = "FastAPILabs.fastapi-vscode" @@ -150,7 +150,10 @@ export async function activate(context: vscode.ExtensionContext) { }) } - const endpointProvider = new EndpointTreeProvider(apps, groupApps(apps)) + const pathOperationProvider = new PathOperationTreeProvider( + apps, + groupApps(apps), + ) const codeLensProvider = new TestCodeLensProvider(parserService, apps) // File watcher for auto-refresh @@ -160,7 +163,7 @@ export async function activate(context: vscode.ExtensionContext) { refreshTimeout = setTimeout(async () => { if (!parserService) return const newApps = await discoverFastAPIApps(parserService) - endpointProvider.setApps(newApps, groupApps(newApps)) + pathOperationProvider.setApps(newApps, groupApps(newApps)) codeLensProvider.setApps(newApps) }, 300) } @@ -176,8 +179,8 @@ export async function activate(context: vscode.ExtensionContext) { ) // Tree view - const treeView = vscode.window.createTreeView("endpoint-explorer", { - treeDataProvider: endpointProvider, + const treeView = vscode.window.createTreeView("path-operation-explorer", { + treeDataProvider: pathOperationProvider, }) treeView.onDidChangeVisibility((e) => { @@ -294,7 +297,7 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push( watcher, treeView, - registerCommands(endpointProvider, codeLensProvider, groupApps), + registerCommands(pathOperationProvider, codeLensProvider, groupApps), { dispose: () => clearInterval(telemetryFlushInterval) }, ) } @@ -371,7 +374,7 @@ function registerCloudCommands( } function registerCommands( - endpointProvider: EndpointTreeProvider, + pathOperationProvider: PathOperationTreeProvider, codeLensProvider: TestCodeLensProvider, groupApps: ( apps: AppDefinition[], @@ -382,19 +385,19 @@ function registerCommands( ): vscode.Disposable { return vscode.Disposable.from( vscode.commands.registerCommand( - "fastapi-vscode.refreshEndpoints", + "fastapi-vscode.refreshPathOperations", async () => { if (!parserService) return clearImportCache() const newApps = await discoverFastAPIApps(parserService) - endpointProvider.setApps(newApps, groupApps(newApps)) + pathOperationProvider.setApps(newApps, groupApps(newApps)) codeLensProvider.setApps(newApps) }, ), vscode.commands.registerCommand( - "fastapi-vscode.goToEndpoint", - (item: EndpointTreeItem) => { + "fastapi-vscode.goToPathOperation", + (item: PathOperationTreeItem) => { if (item.type === "route") { incrementRouteNavigated() navigateToLocation(item.route.location) @@ -403,11 +406,11 @@ function registerCommands( ), vscode.commands.registerCommand( - "fastapi-vscode.searchEndpoints", + "fastapi-vscode.searchPathOperations", async () => { const workspacePrefix = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? "" - const items = collectRoutes(endpointProvider.getApps()) + const items = collectRoutes(pathOperationProvider.getApps()) .map((route) => { const path = stripLeadingDynamicSegments(route.path) return { @@ -425,13 +428,13 @@ function registerCommands( if (items.length === 0) { trackSearchExecuted(0, false) vscode.window.showInformationMessage( - "No FastAPI endpoints found in the workspace.", + "No FastAPI path operations found in the workspace.", ) return } const selected = await vscode.window.showQuickPick(items, { - placeHolder: "Search FastAPI endpoints...", + placeHolder: "Search FastAPI path operations...", }) trackSearchExecuted(items.length, selected !== undefined) if (selected) { @@ -441,8 +444,8 @@ function registerCommands( ), vscode.commands.registerCommand( - "fastapi-vscode.copyEndpointPath", - (item: EndpointTreeItem) => { + "fastapi-vscode.copyPathOperationPath", + (item: PathOperationTreeItem) => { if (item.type === "route") { incrementRouteCopied() vscode.env.clipboard.writeText( @@ -454,7 +457,7 @@ function registerCommands( vscode.commands.registerCommand( "fastapi-vscode.goToRouter", - (item: EndpointTreeItem) => { + (item: PathOperationTreeItem) => { if (item.type === "router") { navigateToLocation(item.router.location) } @@ -470,7 +473,7 @@ function registerCommands( }), vscode.commands.registerCommand("fastapi-vscode.toggleRouters", () => { - endpointProvider.toggleRouters() + pathOperationProvider.toggleRouters() }), vscode.commands.registerCommand( diff --git a/src/test/core/pathUtils.test.ts b/src/test/core/pathUtils.test.ts index e8d1376..d90eaa8 100644 --- a/src/test/core/pathUtils.test.ts +++ b/src/test/core/pathUtils.test.ts @@ -4,7 +4,7 @@ import { findProjectRoot, getPathSegments, isWithinDirectory, - pathMatchesEndpoint, + pathMatchesPathOperation, stripLeadingDynamicSegments, } from "../../core/pathUtils" import { fixtures, nodeFileSystem } from "../testUtils" @@ -197,26 +197,29 @@ suite("pathUtils", () => { }) }) - suite("pathMatchesEndpoint", () => { + suite("pathMatchesPathOperation", () => { test("matches exact static paths", () => { - assert.strictEqual(pathMatchesEndpoint("/items", "/items"), true) - assert.strictEqual(pathMatchesEndpoint("/api/users", "/api/users"), true) + assert.strictEqual(pathMatchesPathOperation("/items", "/items"), true) + assert.strictEqual( + pathMatchesPathOperation("/api/users", "/api/users"), + true, + ) }) test("matches path with single parameter", () => { assert.strictEqual( - pathMatchesEndpoint("/items/123", "/items/{item_id}"), + pathMatchesPathOperation("/items/123", "/items/{item_id}"), true, ) assert.strictEqual( - pathMatchesEndpoint("/items/abc", "/items/{item_id}"), + pathMatchesPathOperation("/items/abc", "/items/{item_id}"), true, ) }) test("matches path with multiple parameters", () => { assert.strictEqual( - pathMatchesEndpoint( + pathMatchesPathOperation( "/users/abc/posts/456", "/users/{user_id}/posts/{post_id}", ), @@ -226,94 +229,103 @@ suite("pathUtils", () => { test("rejects when segment count differs", () => { assert.strictEqual( - pathMatchesEndpoint("/items/123/details", "/items/{item_id}"), + pathMatchesPathOperation("/items/123/details", "/items/{item_id}"), false, ) assert.strictEqual( - pathMatchesEndpoint("/items", "/items/{item_id}"), + pathMatchesPathOperation("/items", "/items/{item_id}"), false, ) }) test("rejects when static segments differ", () => { assert.strictEqual( - pathMatchesEndpoint("/users/123", "/items/{item_id}"), + pathMatchesPathOperation("/users/123", "/items/{item_id}"), false, ) assert.strictEqual( - pathMatchesEndpoint("/api/v1/items", "/api/v2/items"), + pathMatchesPathOperation("/api/v1/items", "/api/v2/items"), false, ) }) test("handles trailing slashes", () => { - assert.strictEqual(pathMatchesEndpoint("/items/", "/items"), true) - assert.strictEqual(pathMatchesEndpoint("/items", "/items/"), true) + assert.strictEqual(pathMatchesPathOperation("/items/", "/items"), true) + assert.strictEqual(pathMatchesPathOperation("/items", "/items/"), true) assert.strictEqual( - pathMatchesEndpoint("/items/123/", "/items/{id}"), + pathMatchesPathOperation("/items/123/", "/items/{id}"), true, ) }) test("handles root path", () => { - assert.strictEqual(pathMatchesEndpoint("/", "/"), true) - assert.strictEqual(pathMatchesEndpoint("", ""), true) + assert.strictEqual(pathMatchesPathOperation("/", "/"), true) + assert.strictEqual(pathMatchesPathOperation("", ""), true) }) test("rejects empty path against non-empty", () => { assert.strictEqual( - pathMatchesEndpoint("/items/123", "/items/{item_id}"), + pathMatchesPathOperation("/items/123", "/items/{item_id}"), true, ) assert.strictEqual( - pathMatchesEndpoint("/items/", "/items/{item_id}"), + pathMatchesPathOperation("/items/", "/items/{item_id}"), false, ) }) - test("matches paths with dynamic prefix in endpoint", () => { + test("matches paths with dynamic prefix in path operation", () => { assert.strictEqual( - pathMatchesEndpoint( + pathMatchesPathOperation( "/items/123", "{settings.API_V1_STR}/items/{item_id}", ), true, ) assert.strictEqual( - pathMatchesEndpoint("/api/v2/users", "{BASE}/users"), + pathMatchesPathOperation("/api/v2/users", "{BASE}/users"), + false, + ) + assert.strictEqual( + pathMatchesPathOperation("/users", "{BASE}/users"), + true, + ) + assert.strictEqual( + pathMatchesPathOperation("/items", "{BASE}/users"), false, ) - assert.strictEqual(pathMatchesEndpoint("/users", "{BASE}/users"), true) - assert.strictEqual(pathMatchesEndpoint("/items", "{BASE}/users"), false) }) test("matches paths with dynamic prefix in test path (f-strings)", () => { assert.strictEqual( - pathMatchesEndpoint( + pathMatchesPathOperation( "{settings.API_V1_STR}/apps/{app.id}/environment-variables", "/apps/{app_id}/environment-variables", ), true, ) assert.strictEqual( - pathMatchesEndpoint( + pathMatchesPathOperation( "{settings.API}/items/{item_id}", "{BASE}/items/{id}", ), true, ) assert.strictEqual( - pathMatchesEndpoint("{BASE}/users/{id}", "/items/{item_id}"), + pathMatchesPathOperation("{BASE}/users/{id}", "/items/{item_id}"), false, ) }) test("strips query strings from test path", () => { assert.strictEqual( - pathMatchesEndpoint("/teams/?owner=true&order_by=created_at", "/teams"), + pathMatchesPathOperation( + "/teams/?owner=true&order_by=created_at", + "/teams", + ), true, ) - assert.strictEqual(pathMatchesEndpoint("/?page=1", "/"), true) + assert.strictEqual(pathMatchesPathOperation("/?page=1", "/"), true) }) }) }) diff --git a/src/test/fixtures/mockEndpointData.ts b/src/test/fixtures/mockPathOperationData.ts similarity index 98% rename from src/test/fixtures/mockEndpointData.ts rename to src/test/fixtures/mockPathOperationData.ts index db91bd6..9cf90df 100644 --- a/src/test/fixtures/mockEndpointData.ts +++ b/src/test/fixtures/mockPathOperationData.ts @@ -1,5 +1,5 @@ import type { AppDefinition } from "../../core/types" -import type { EndpointTreeItem } from "../../vscode/endpointTreeProvider" +import type { PathOperationTreeItem } from "../../vscode/pathOperationTreeProvider" export const mockApps: AppDefinition[] = [ { @@ -268,7 +268,7 @@ export const mockApps: AppDefinition[] = [ */ export function groupAppsByWorkspace( apps: AppDefinition[], -): EndpointTreeItem[] { +): PathOperationTreeItem[] { const workspaceMap = new Map() for (const app of apps) { diff --git a/src/test/providers/endpointTreeProvider.test.ts b/src/test/providers/endpointTreeProvider.test.ts index 42c0e82..c5728f0 100644 --- a/src/test/providers/endpointTreeProvider.test.ts +++ b/src/test/providers/endpointTreeProvider.test.ts @@ -5,12 +5,15 @@ import type { RouterDefinition, } from "../../core/types" import { - EndpointTreeProvider, getAppLabel, getRouteLabel, getRouterLabel, -} from "../../vscode/endpointTreeProvider" -import { groupAppsByWorkspace, mockApps } from "../fixtures/mockEndpointData" + PathOperationTreeProvider, +} from "../../vscode/pathOperationTreeProvider" +import { + groupAppsByWorkspace, + mockApps, +} from "../fixtures/mockPathOperationData" function makeRoute(method: string, path: string): RouteDefinition { return { @@ -130,11 +133,11 @@ suite("getRouteLabel", () => { }) }) -suite("EndpointTreeProvider", () => { - let provider: EndpointTreeProvider +suite("PathOperationTreeProvider", () => { + let provider: PathOperationTreeProvider setup(() => { - provider = new EndpointTreeProvider(mockApps) + provider = new PathOperationTreeProvider(mockApps) }) test("getChildren returns apps at root level", () => { @@ -273,7 +276,7 @@ suite("EndpointTreeProvider", () => { assert.ok(treeItem.command, "Route should have a command") assert.strictEqual( treeItem.command?.command, - "fastapi-vscode.goToEndpoint", + "fastapi-vscode.goToPathOperation", ) } }) @@ -305,7 +308,7 @@ suite("EndpointTreeProvider", () => { }) test("getChildren returns message when no apps", () => { - const emptyProvider = new EndpointTreeProvider([]) + const emptyProvider = new PathOperationTreeProvider([]) const roots = emptyProvider.getChildren() assert.strictEqual(roots.length, 1, "Should return one message item") assert.strictEqual(roots[0].type, "message") @@ -322,7 +325,7 @@ suite("EndpointTreeProvider", () => { }) test("getTreeItem for message type", () => { - const emptyProvider = new EndpointTreeProvider([]) + const emptyProvider = new PathOperationTreeProvider([]) const msg = emptyProvider.getChildren()[0] assert.strictEqual( emptyProvider.getTreeItem(msg).label, @@ -332,7 +335,7 @@ suite("EndpointTreeProvider", () => { test("getTreeItem for workspace type", () => { const wsRoots = groupAppsByWorkspace(mockApps) - const wsProvider = new EndpointTreeProvider(mockApps, wsRoots) + const wsProvider = new PathOperationTreeProvider(mockApps, wsRoots) const workspace = wsProvider .getChildren() .find((r) => r.type === "workspace") @@ -362,7 +365,7 @@ suite("EndpointTreeProvider", () => { } const app = makeApp("app", "main.py") app.routers = [parentRouter] - const p = new EndpointTreeProvider([app]) + const p = new PathOperationTreeProvider([app]) assert.strictEqual( p.getTreeItem({ type: "router", router: childRouter }).label, "/settings", @@ -380,11 +383,11 @@ suite("EndpointTreeProvider", () => { }) test("dispose cleans up", () => { - new EndpointTreeProvider([]).dispose() + new PathOperationTreeProvider([]).dispose() }) test("setApps updates apps and refreshes tree", () => { - const p = new EndpointTreeProvider([]) + const p = new PathOperationTreeProvider([]) assert.strictEqual(p.getChildren()[0].type, "message") p.setApps(mockApps) assert.strictEqual(p.getChildren()[0].type, "app") @@ -415,7 +418,7 @@ suite("EndpointTreeProvider", () => { test("getParent returns workspace for app in multi-root", () => { const wsRoots = groupAppsByWorkspace(mockApps) - const wsProvider = new EndpointTreeProvider(mockApps, wsRoots) + const wsProvider = new PathOperationTreeProvider(mockApps, wsRoots) const workspace = wsProvider .getChildren() .find((r) => r.type === "workspace")! @@ -442,7 +445,7 @@ suite("EndpointTreeProvider", () => { } const app = makeApp("app", "main.py") app.routers = [parentRouter] - const p = new EndpointTreeProvider([app]) + const p = new PathOperationTreeProvider([app]) const parent = p.getParent({ type: "router", router: childRouter }) assert.strictEqual(parent?.type, "router") }) diff --git a/src/test/providers/testCodeLensProvider.test.ts b/src/test/providers/testCodeLensProvider.test.ts index 1b16e5d..adefffb 100644 --- a/src/test/providers/testCodeLensProvider.test.ts +++ b/src/test/providers/testCodeLensProvider.test.ts @@ -171,7 +171,7 @@ def test_create_item(): test("creates CodeLens for routes in routers", async () => { // Route path in the definition is just the route path, not the full path with prefix - // The pathMatchesEndpoint function handles matching + // The pathMatchesPathOperation function handles matching const router = createRouter("/api", [createRoute("GET", "/users")]) const app = createMockApp([], [router]) provider.setApps([app]) diff --git a/src/vscode/endpointTreeProvider.ts b/src/vscode/pathOperationTreeProvider.ts similarity index 91% rename from src/vscode/endpointTreeProvider.ts rename to src/vscode/pathOperationTreeProvider.ts index cbc69cc..f709919 100644 --- a/src/vscode/endpointTreeProvider.ts +++ b/src/vscode/pathOperationTreeProvider.ts @@ -15,7 +15,7 @@ import type { RouterDefinition, } from "../core/types" -export type EndpointTreeItem = +export type PathOperationTreeItem = | { type: "workspace"; label: string; apps: AppDefinition[] } | { type: "app"; app: AppDefinition } | { type: "router"; router: RouterDefinition } @@ -81,7 +81,7 @@ export function getRouteLabel(route: RouteDefinition): string { function sortedChildren( routers: RouterDefinition[], routes: RouteDefinition[], -): EndpointTreeItem[] { +): PathOperationTreeItem[] { return [ ...routers .map((router) => ({ type: "router" as const, router })) @@ -100,22 +100,23 @@ function sortedChildren( ] } -export class EndpointTreeProvider - implements TreeDataProvider +export class PathOperationTreeProvider + implements TreeDataProvider { - private _onDidChangeTreeData: EventEmitter = - new EventEmitter() + private _onDidChangeTreeData: EventEmitter< + PathOperationTreeItem | undefined + > = new EventEmitter() readonly onDidChangeTreeData = this._onDidChangeTreeData.event private apps: AppDefinition[] = [] - private roots: EndpointTreeItem[] = [] + private roots: PathOperationTreeItem[] = [] // VS Code caches collapsible state by item id, so toggling routersExpanded // alone won't re-render. Bumping toggleCount changes the id, forcing a reset. private routersExpanded = false private toggleCount = 0 - constructor(apps: AppDefinition[] = [], roots?: EndpointTreeItem[]) { + constructor(apps: AppDefinition[] = [], roots?: PathOperationTreeItem[]) { this.apps = apps this.roots = roots ?? apps.map((app) => ({ type: "app" as const, app })) } @@ -124,7 +125,7 @@ export class EndpointTreeProvider return this.apps } - setApps(apps: AppDefinition[], roots?: EndpointTreeItem[]): void { + setApps(apps: AppDefinition[], roots?: PathOperationTreeItem[]): void { this.apps = apps this.roots = roots ?? apps.map((app) => ({ type: "app" as const, app })) this.refresh() @@ -142,7 +143,7 @@ export class EndpointTreeProvider return findRouter(this.apps, (router) => router.routes.includes(target)) } - getParent(element: EndpointTreeItem): EndpointTreeItem | undefined { + getParent(element: PathOperationTreeItem): PathOperationTreeItem | undefined { switch (element.type) { case "message": case "workspace": @@ -175,7 +176,7 @@ export class EndpointTreeProvider } } - getChildren(element?: EndpointTreeItem): EndpointTreeItem[] { + getChildren(element?: PathOperationTreeItem): PathOperationTreeItem[] { if (!element) { if (this.apps.length === 0) { return [{ type: "message", text: "No FastAPI app found" }] @@ -198,7 +199,7 @@ export class EndpointTreeProvider } } - getTreeItem(element: EndpointTreeItem): TreeItem { + getTreeItem(element: PathOperationTreeItem): TreeItem { switch (element.type) { case "message": { const item = new TreeItem(element.text) @@ -256,7 +257,7 @@ export class EndpointTreeProvider `${element.route.method} ${element.route.path}\n\nFunction: ${element.route.functionName}\nFile: ${element.route.location.filePath}:${element.route.location.line}`, ) routeItem.command = { - command: "fastapi-vscode.goToEndpoint", + command: "fastapi-vscode.goToPathOperation", title: "Go to Definition", arguments: [element], } diff --git a/src/vscode/testCodeLensProvider.ts b/src/vscode/testCodeLensProvider.ts index 1b57f3c..a7c89c7 100644 --- a/src/vscode/testCodeLensProvider.ts +++ b/src/vscode/testCodeLensProvider.ts @@ -18,7 +18,7 @@ import { extractPathFromNode, findNodesByType } from "../core/extractors" import { ROUTE_METHODS } from "../core/internal" import type { Parser } from "../core/parser" import { - pathMatchesEndpoint, + pathMatchesPathOperation, stripLeadingDynamicSegments, } from "../core/pathUtils" import { collectRoutes } from "../core/treeUtils" @@ -157,7 +157,7 @@ export class TestCodeLensProvider implements CodeLensProvider { .filter( (route) => route.method.toLowerCase() === testMethod.toLowerCase() && - pathMatchesEndpoint(testPath, route.path), + pathMatchesPathOperation(testPath, route.path), ) .map((route) => route.location) } From 2eaaea763e650368a7c4589873d7367becfda7bc Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Tue, 17 Feb 2026 13:22:52 -0800 Subject: [PATCH 2/2] Rename file --- ...ntTreeProvider.test.ts => pathOperationTreeProvider.test.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/providers/{endpointTreeProvider.test.ts => pathOperationTreeProvider.test.ts} (99%) diff --git a/src/test/providers/endpointTreeProvider.test.ts b/src/test/providers/pathOperationTreeProvider.test.ts similarity index 99% rename from src/test/providers/endpointTreeProvider.test.ts rename to src/test/providers/pathOperationTreeProvider.test.ts index 01a8dd6..a044fce 100644 --- a/src/test/providers/endpointTreeProvider.test.ts +++ b/src/test/providers/pathOperationTreeProvider.test.ts @@ -173,7 +173,7 @@ suite("PathOperationTreeProvider", () => { makeRoute("POST", "/users"), makeRoute("PUT", "/users"), ] - const p = new EndpointTreeProvider([app]) + const p = new PathOperationTreeProvider([app]) const children = p.getChildren(p.getChildren()[0]) const labels = children.map((c) => c.type === "route" ? getRouteLabel(c.route) : "",