From ebf73aa7b66b39d5f57267bffe1a37fc5371808d Mon Sep 17 00:00:00 2001 From: slugb0t Date: Thu, 4 Jun 2026 00:31:05 -0700 Subject: [PATCH] fix: :bug: address Sourcery review -- gate Nitro scan on imports.d.ts and pre-index explicit imports --- .../languages/typescript/nuxt-auto-imports.ts | 26 ++++++++++----- .../languages/typescript/scope-resolver.ts | 33 ++++++++++--------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/gitnexus/src/core/ingestion/languages/typescript/nuxt-auto-imports.ts b/gitnexus/src/core/ingestion/languages/typescript/nuxt-auto-imports.ts index 99bb92036..5dd31c9e7 100644 --- a/gitnexus/src/core/ingestion/languages/typescript/nuxt-auto-imports.ts +++ b/gitnexus/src/core/ingestion/languages/typescript/nuxt-auto-imports.ts @@ -62,18 +62,24 @@ export interface NuxtAutoImportConfig { * Load the Nuxt auto-import map for `repoRoot`. * * Returns null when: - * - `.nuxt/imports.d.ts` does not exist (non-Nuxt project), or - * - the file exists but yields zero project-local entries. + * - `.nuxt/imports.d.ts` does not exist (non-Nuxt project or pre-build), or + * - the file exists but yields zero project-local entries and `server/utils` + * is absent or empty. + * + * The `server/utils` scan is only attempted when `.nuxt/imports.d.ts` was + * successfully read, confirming this is an initialized Nuxt project. This + * avoids partial results from repos that have a `server/utils` directory but + * are not Nuxt projects. */ export async function loadNuxtAutoImports(repoRoot: string): Promise { const byLocalName = new Map(); - await collectImportsDts(repoRoot, byLocalName); + const nuxtInitialized = await collectImportsDts(repoRoot, byLocalName); - // Only attempt the server/utils scan when we confirmed this is a Nuxt project - // (imports.d.ts was present and populated the map, or at least the file existed). - const nuxtDirExists = await dirExists(path.join(repoRoot, '.nuxt')); - if (nuxtDirExists) { + // Only scan server/utils when imports.d.ts was present, confirming this is + // an initialized Nuxt project. Without this gate, a non-Nuxt repo with a + // server/utils directory would get spurious Nitro auto-import edges. + if (nuxtInitialized) { await collectNitroServerUtils(repoRoot, byLocalName); } @@ -93,16 +99,17 @@ export async function loadNuxtAutoImports(repoRoot: string): Promise, -): Promise { +): Promise { const importsPath = path.join(repoRoot, '.nuxt', 'imports.d.ts'); let content: string; try { content = await fs.readFile(importsPath, 'utf-8'); } catch { - return; + return false; } const nuxtDir = path.join(repoRoot, '.nuxt'); @@ -136,6 +143,7 @@ async function collectImportsDts( } } } + return true; } // ---- server/utils (Nitro auto-imports) -------------------------------------- diff --git a/gitnexus/src/core/ingestion/languages/typescript/scope-resolver.ts b/gitnexus/src/core/ingestion/languages/typescript/scope-resolver.ts index 2ab17bdce..0c94bf455 100644 --- a/gitnexus/src/core/ingestion/languages/typescript/scope-resolver.ts +++ b/gitnexus/src/core/ingestion/languages/typescript/scope-resolver.ts @@ -187,6 +187,22 @@ const typescriptScopeResolver: ScopeResolver = { // Pre-index: localName -> entry for fast lookup during content scan. const { byLocalName } = autoImports; + // Pre-build a file -> explicit-import-targets index so the per-file + // lookup below is O(1) rather than scanning all import edges every iteration. + const explicitImportsByFile = new Map>(); + for (const [scopeId, edges] of indexes.imports) { + const scope = indexes.scopeTree.getScope(scopeId); + if (!scope?.filePath) continue; + let targets = explicitImportsByFile.get(scope.filePath); + if (!targets) { + targets = new Set(); + explicitImportsByFile.set(scope.filePath, targets); + } + for (const edge of edges) { + if (edge.targetFile !== null) targets.add(edge.targetFile); + } + } + // Regex matches bare identifier call sites: word-boundary + name + "(". // Excludes `new X(` (constructor calls are not free-function auto-imports). const CALL_RE = /(?(); - for (const [scopeId, edges] of indexes.imports) { - const scope = indexes.scopeTree.getScope(scopeId); - if (scope?.filePath !== filePath) continue; - for (const edge of edges) { - if (edge.targetFile !== null) explicitImports.add(edge.targetFile); - } - } + const explicitImports = explicitImportsByFile.get(filePath) ?? new Set(); const fileId = generateId('File', filePath); // Track (sourceFile) pairs already handled for this caller to avoid @@ -214,11 +222,6 @@ const typescriptScopeResolver: ScopeResolver = { const emittedImports = new Set(); const emittedCalls = new Set(); - // Skip source files: a file cannot auto-import its own exports. - // Without this guard the content scanner would match each exported - // function's own name inside its definition body and emit a self-loop. - const selfPath = filePath; - CALL_RE.lastIndex = 0; let m: RegExpExecArray | null; while ((m = CALL_RE.exec(content)) !== null) { @@ -230,7 +233,7 @@ const typescriptScopeResolver: ScopeResolver = { // Skip when the file already has an explicit import from this source, // or when the file IS the source (a file cannot auto-import itself). - if (explicitImports.has(sourceFile) || sourceFile === selfPath) continue; + if (explicitImports.has(sourceFile) || sourceFile === filePath) continue; // Emit one IMPORTS edge per (caller, sourceFile) pair. if (!emittedImports.has(sourceFile)) {