Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
896d989
ParserService
savannahostrowski Jan 7, 2026
25a1539
Process decorators
savannahostrowski Jan 8, 2026
e67c9ba
Extract routers
savannahostrowski Jan 8, 2026
b640dc6
Import extractor
savannahostrowski Jan 8, 2026
a1e063e
Include router extractor
savannahostrowski Jan 8, 2026
471aecc
Single file works
savannahostrowski Jan 8, 2026
7e3f0aa
Add import resolver
savannahostrowski Jan 8, 2026
1e338b4
Okay it works
savannahostrowski Jan 8, 2026
1e80fd5
Fix up UI
savannahostrowski Jan 8, 2026
e6ebbe9
Handle dynamic paths
savannahostrowski Jan 9, 2026
299f522
Add entrypoint setting and order alphabetically
savannahostrowski Jan 9, 2026
da7a596
Add tests
savannahostrowski Jan 9, 2026
78c59a2
Big refactor
savannahostrowski Jan 12, 2026
968f6ba
More cleanup
savannahostrowski Jan 12, 2026
ba8eaf4
Clean up command palette registration
savannahostrowski Jan 12, 2026
271680f
Fix packaging
savannahostrowski Jan 12, 2026
ad1ad45
Fix publisher name
savannahostrowski Jan 12, 2026
f46fe81
Clean up esbuild
savannahostrowski Jan 13, 2026
0218981
Clean up command palette
savannahostrowski Jan 13, 2026
00284a9
Add string extraction helper
savannahostrowski Jan 13, 2026
cc8f9d0
Remove router prefix menu
savannahostrowski Jan 13, 2026
298f265
Clean up esbuild
savannahostrowski Jan 14, 2026
d9685f7
Add support for namespace package
savannahostrowski Jan 14, 2026
502fa95
Clean up fixtures
savannahostrowski Jan 14, 2026
3a4bb8b
Refactor tests
savannahostrowski Jan 14, 2026
4a638d7
extension.ts cleanup
savannahostrowski Jan 14, 2026
7a233eb
Remove non-dynamic prefix stripping
savannahostrowski Jan 14, 2026
2041ca8
Add test
savannahostrowski Jan 14, 2026
d16a812
Fix test
savannahostrowski Jan 14, 2026
138190d
Clean up extractors
savannahostrowski Jan 14, 2026
082959b
Even more cleanup
savannahostrowski Jan 14, 2026
daa7acc
More TS syntactic sugar
savannahostrowski Jan 14, 2026
b048d67
Restructure test files
savannahostrowski Jan 14, 2026
b99e2be
Fix memory leak on deactivation
savannahostrowski Jan 14, 2026
c5b2e12
Reconcile bundle refactoring
savannahostrowski Jan 14, 2026
c9ca925
Merge main
savannahostrowski Jan 14, 2026
2698001
Fix removal of copy router prefix
savannahostrowski Jan 14, 2026
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
13 changes: 12 additions & 1 deletion .vscodeignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
# Source files (bundled into dist/)
src/**
esbuild.js
wasm/**

# Dependencies
node_modules/**
!node_modules/web-tree-sitter/package.json
!node_modules/web-tree-sitter/web-tree-sitter.cjs

# Build artifacts
dist/test/**
dist/*.map

# Config/meta files
.*/
.*
*.lock*
*.json
!package.json
esbuild.js
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
# FastAPI VS Code Extension

A VS Code extension for FastAPI development.
A VS Code extension for FastAPI development that discovers and displays your API endpoints in a tree view.

## Features

- Hello World command (example)
- Automatic discovery of FastAPI routes and routers
- Tree view showing all endpoints organized by router
- Click to navigate to route definitions
- Supports `include_router` chains with prefix resolution

## Settings

| Setting | Description | Default |
|---------|-------------|---------|
| `fastapi.entryPoint` | Path to the main FastAPI application file (e.g., `src/main.py`). If not set, the extension searches common locations: `main.py`, `app/main.py`, `api/main.py`, `src/main.py`, `backend/app/main.py`. | `""` (auto-detect) |

**Note:** Currently the extension discovers one FastAPI app per workspace folder. If you have multiple apps, use separate workspace folders or configure `fastapi.entryPoint` to point to your primary app.

## Development

Expand Down
9 changes: 6 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 38 additions & 6 deletions esbuild.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,59 @@
import { copyFileSync, globSync, mkdirSync } from "node:fs"
import path from "node:path"
import esbuild from "esbuild"

const production = process.argv.includes("--production")
const watch = process.argv.includes("--watch")

function copyWasmFiles() {
const wasmDestDir = path.join(import.meta.dirname, "dist", "wasm")
mkdirSync(wasmDestDir, { recursive: true })

// web-tree-sitter.wasm from node_modules
const coreSrc = path.join(
import.meta.dirname,
"node_modules",
"web-tree-sitter",
"web-tree-sitter.wasm",
)
copyFileSync(coreSrc, path.join(wasmDestDir, "web-tree-sitter.wasm"))
console.log("Copied web-tree-sitter.wasm -> dist/wasm/")

// tree-sitter-python.wasm from wasm/ directory (checked into repo)
const pythonSrc = path.join(
import.meta.dirname,
"wasm",
"tree-sitter-python.wasm",
)
copyFileSync(pythonSrc, path.join(wasmDestDir, "tree-sitter-python.wasm"))
console.log("Copied tree-sitter-python.wasm -> dist/wasm/")
}

async function main() {
copyWasmFiles()

const entryPoints = ["src/extension.ts"]
if (!production) {
entryPoints.push(...globSync("src/test/**/*.test.ts"))
}

const ctx = await esbuild.context({
entryPoints: [
"src/extension.ts",
"src/test/extension.test.ts",
"src/test/EndpointTreeProvider.test.ts",
],
entryPoints,
bundle: true,
format: "cjs",
minify: production,
sourcemap: !production,
sourcesContent: false,
platform: "node",
target: "node20",
treeShaking: true,
outdir: "dist",
outbase: "src",
external: ["vscode"],
external: ["vscode", "web-tree-sitter"],
logLevel: "info",
define: {
"process.env.NODE_ENV": production ? '"production"' : '"development"',
__DIST_ROOT__: JSON.stringify(path.join(import.meta.dirname, "dist")),
},
})

Expand Down
83 changes: 51 additions & 32 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,50 @@
"name": "fastapi-vscode",
"displayName": "FastAPI Extension",
"description": "VS Code extension for FastAPI development",
"version": "0.0.1",
"version": "0.0.2",
"publisher": "FastAPILabs",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/fastapi/fastapi-vscode"
},
"bugs": {
"url": "https://github.com/fastapi/fastapi-vscode/issues"
},
"homepage": "https://github.com/fastapi/fastapi-vscode#readme",
"engines": {
"vscode": "^1.85.0"
},
"categories": [
"Other"
],
"main": "./dist/extension.js",
"activationEvents": [
"workspaceContains:**/*.py"
],
"main": "./dist/extension.js",
"categories": [
"Other"
],
"contributes": {
"commands": [
{
"command": "fastapi-vscode.refreshEndpoints",
"title": "Refresh Endpoints",
"category": "FastAPI",
"icon": "$(refresh)"
},
{
"command": "fastapi-vscode.goToEndpoint",
"title": "Go to Definition"
"command": "fastapi-vscode.toggleRouters",
"title": "Toggle Expand/Collapse Routers",
"category": "FastAPI",
"icon": "$(fold)"
},
{
"command": "fastapi-vscode.copyEndpointPath",
"title": "Copy Path"
"title": "Copy Endpoint Path",
"category": "FastAPI"
},
{
"command": "fastapi-vscode.goToRouter",
"title": "Go to Router Definition"
},
{
"command": "fastapi-vscode.copyRouterPrefix",
"title": "Copy Prefix"
"title": "Go to Router Definition",
"category": "FastAPI"
},
{
"command": "fastapi-vscode.reportIssue",
Expand All @@ -44,19 +54,29 @@
}
],
"menus": {
"commandPalette": [
{
"command": "fastapi-vscode.copyEndpointPath",
"when": "false"
},
{
"command": "fastapi-vscode.goToRouter",
"when": "false"
}
],
"view/title": [
{
"command": "fastapi-vscode.refreshEndpoints",
"when": "view == endpoint-explorer",
"group": "navigation"
"group": "navigation@1"
},
{
"command": "fastapi-vscode.toggleRouters",
"when": "view == endpoint-explorer",
"group": "navigation@2"
}
],
"view/item/context": [
{
"command": "fastapi-vscode.goToEndpoint",
"when": "view == endpoint-explorer && viewItem == route",
"group": "navigation"
},
{
"command": "fastapi-vscode.copyEndpointPath",
"when": "view == endpoint-explorer && viewItem == route",
Expand All @@ -66,11 +86,6 @@
"command": "fastapi-vscode.goToRouter",
"when": "view == endpoint-explorer && viewItem == router",
"group": "navigation"
},
{
"command": "fastapi-vscode.copyRouterPrefix",
"when": "view == endpoint-explorer && viewItem == router",
"group": "navigation"
}
]
},
Expand All @@ -90,6 +105,16 @@
"name": "Endpoint Explorer"
}
]
},
"configuration": {
"title": "FastAPI",
"properties": {
"fastapi.entryPoint": {
"type": "string",
"default": "",
"description": "Path to the main FastAPI application file (e.g., 'src/main.py'). If not set, the extension will search common locations."
}
}
}
},
"scripts": {
Expand All @@ -115,15 +140,9 @@
"lint-staged": "^16.2.7",
"typescript": "^5.0.0"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/fastapi/fastapi-vscode"
},
"bugs": {
"url": "https://github.com/fastapi/fastapi-vscode/issues"
"dependencies": {
"web-tree-sitter": "^0.26.3"
},
"homepage": "https://github.com/fastapi/fastapi-vscode#readme",
"lint-staged": {
"*.{ts,js,json}": [
"biome check --write"
Expand Down
1 change: 0 additions & 1 deletion src/commands/index.ts

This file was deleted.

64 changes: 64 additions & 0 deletions src/core/analyzer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Analyzer module to extract FastAPI-related information from syntax trees.
*/

import { readFileSync } from "node:fs"
import type { Tree } from "web-tree-sitter"
import {
decoratorExtractor,
findNodesByType,
importExtractor,
includeRouterExtractor,
mountExtractor,
routerExtractor,
} from "./extractors.js"
import type { FileAnalysis } from "./internal"
import type { Parser } from "./parser.js"

function notNull<T>(value: T | null): value is T {
return value !== null
}

/** Analyze a syntax tree and extract FastAPI-related information */
export function analyzeTree(tree: Tree, filePath: string): FileAnalysis {
const rootNode = tree.rootNode

// Get all decorated definitions (functions and classes with decorators)
const decoratedDefs = findNodesByType(rootNode, "decorated_definition")
const routes = decoratedDefs.map(decoratorExtractor).filter(notNull)

// Get all router assignments
const assignments = findNodesByType(rootNode, "assignment")
const routers = assignments.map(routerExtractor).filter(notNull)

// Get all include_router and mount calls
const callNodes = findNodesByType(rootNode, "call")
const includeRouters = callNodes.map(includeRouterExtractor).filter(notNull)
const mounts = callNodes.map(mountExtractor).filter(notNull)

// Get all import statements
const importNodes = findNodesByType(rootNode, "import_statement")
const importFromNodes = findNodesByType(rootNode, "import_from_statement")
const imports = [...importNodes, ...importFromNodes]
.map(importExtractor)
.filter(notNull)

return { filePath, routes, routers, includeRouters, mounts, imports }
}

/** Analyze a file given its path and a parser instance */
export function analyzeFile(
filePath: string,
parser: Parser,
): FileAnalysis | null {
try {
const code = readFileSync(filePath, "utf-8")
const tree = parser.parse(code)
if (!tree) {
return null
}
return analyzeTree(tree, filePath)
} catch {
return null
}
}
Loading