diff --git a/src/core/analyzer.ts b/src/core/analyzer.ts index ebd7b83..9f53fac 100644 --- a/src/core/analyzer.ts +++ b/src/core/analyzer.ts @@ -4,6 +4,7 @@ import { readFileSync } from "node:fs" import type { Tree } from "web-tree-sitter" +import { logError } from "../utils/logger" import { decoratorExtractor, findNodesByType, @@ -55,10 +56,12 @@ export function analyzeFile( const code = readFileSync(filePath, "utf-8") const tree = parser.parse(code) if (!tree) { + logError(`Failed to parse file: "${filePath}"`) return null } return analyzeTree(tree, filePath) - } catch { + } catch (error) { + logError(`Error reading file: "${filePath}"`, error) return null } } diff --git a/src/core/routerResolver.ts b/src/core/routerResolver.ts index 9946cb8..e92ca9a 100644 --- a/src/core/routerResolver.ts +++ b/src/core/routerResolver.ts @@ -1,5 +1,6 @@ import { existsSync } from "node:fs" import { isAbsolute, join } from "node:path" +import { log } from "../utils/logger" import { analyzeFile } from "./analyzer" import { resolveNamedImport, resolveRouterFromInit } from "./importResolver" import type { FileAnalysis, RouterInfo, RouterNode } from "./internal" @@ -62,10 +63,12 @@ function buildRouterGraphInternal( } if (!existsSync(resolvedEntryFile)) { + log(`File not found: "${entryFile}"`) return null } // Prevent infinite recursion on circular imports if (visited.has(resolvedEntryFile)) { + log(`Skipping already visited file: "${resolvedEntryFile}"`) return null } @@ -74,9 +77,14 @@ function buildRouterGraphInternal( // Analyze the entry file let analysis = analyzeFile(resolvedEntryFile, parser) if (!analysis) { + log(`Failed to analyze file: "${resolvedEntryFile}"`) return null } + log( + `Analyzed "${resolvedEntryFile}": ${analysis.routes.length} routes, ${analysis.routers.length} routers, ${analysis.includeRouters.length} include_router calls`, + ) + // Find FastAPI instantiation (filter by targetVariable if specified) let appRouter = findAppRouter(analysis.routers, targetVariable) @@ -130,6 +138,9 @@ function buildRouterGraphInternal( // Process include_router calls to find child routers for (const include of analysis.includeRouters) { + log( + `Resolving include_router: ${include.router} (prefix: ${include.prefix || "none"})`, + ) const childRouter = resolveRouterReference( include.router, analysis, @@ -220,6 +231,7 @@ function resolveRouterReference( ) if (!matchingImport) { + log(`No import found for router reference: ${reference}`) return null } @@ -236,6 +248,7 @@ function resolveRouterReference( ) if (!importedFilePath) { + log(`Could not resolve import: ${matchingImport.modulePath}`) return null } diff --git a/src/extension.ts b/src/extension.ts index 281ef59..2e14d32 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -13,6 +13,7 @@ import { EndpointTreeProvider, } from "./providers/EndpointTreeProvider" import { TestCodeLensProvider } from "./providers/TestCodeLensProvider" +import { disposeLogger, log } from "./utils/logger" let parserService: Parser | null = null @@ -25,7 +26,13 @@ function navigateToLocation(location: SourceLocation): void { } export async function activate(context: vscode.ExtensionContext) { - // Initialize parser + const extensionVersion = + vscode.extensions.getExtension("FastAPILabs.fastapi-vscode")?.packageJSON + ?.version ?? "unknown" + log( + `FastAPI extension ${extensionVersion} activated (VS Code ${vscode.version})`, + ) + parserService = new Parser() await parserService.init({ core: vscode.Uri.joinPath( @@ -166,7 +173,9 @@ function registerCommands( } export function deactivate() { + log("Extension deactivated") parserService?.dispose() parserService = null clearImportCache() + disposeLogger() } diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..ca29328 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,36 @@ +/** + * Output channel logger for the FastAPI extension. + * Provides visibility into extension activity for troubleshooting. + * + * Uses LogOutputChannel for colored log levels and automatic timestamps. + */ + +import * as vscode from "vscode" + +let outputChannel: vscode.LogOutputChannel | null = null + +function getOutputChannel(): vscode.LogOutputChannel { + if (!outputChannel) { + outputChannel = vscode.window.createOutputChannel("FastAPI", { log: true }) + } + return outputChannel +} + +export function log(message: string): void { + getOutputChannel().info(message) +} + +export function logError(message: string, error?: unknown): void { + if (error instanceof Error) { + getOutputChannel().error(`${message}: ${error.message}`) + } else if (error !== undefined) { + getOutputChannel().error(`${message}: ${String(error)}`) + } else { + getOutputChannel().error(message) + } +} + +export function disposeLogger(): void { + outputChannel?.dispose() + outputChannel = null +}