From 7d8743327a4e5576971ed85581de76e803540b71 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Wed, 24 Jun 2026 08:46:47 +0000 Subject: [PATCH] fix(ingestion): let type-annotated params shadow Nuxt auto-imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hasLocalBindingInScopeChain only consulted scope.bindings, but type-annotated function parameters live in scope.typeBindings (the TS scope query records them as `@type-binding.parameter`, not `@declaration`). A parameter named like a composable therefore failed to suppress the auto-import, leaking a spurious CALLS edge. Also check scope.typeBindings for the name (same-file scopes only). typeBindings holds value-space binders' type facts (parameter annotations, `self`, variable annotations) and never a pure type that belongs to callable space, so this cannot over-suppress a real auto-import. Documents the residual: function-typed params (`p: () => void`), untyped params, destructured locals, and catch-clause vars are captured by neither map and still leak — closing that needs shared scope-query changes beyond this feature, left as a follow-up. Also adds a no-vacuous-pass guard to the shadowing/noise test. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01T4W25WLfYD1JNy8icxeLPU --- .../languages/typescript/scope-resolver.ts | 20 +++++++++++++++++++ .../nuxt-auto-imports/pages/param-typed.ts | 3 +++ .../integration/resolvers/nuxt-scope.test.ts | 13 ++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 gitnexus/test/fixtures/lang-resolution/nuxt-auto-imports/pages/param-typed.ts diff --git a/gitnexus/src/core/ingestion/languages/typescript/scope-resolver.ts b/gitnexus/src/core/ingestion/languages/typescript/scope-resolver.ts index 09301de71..91c147535 100644 --- a/gitnexus/src/core/ingestion/languages/typescript/scope-resolver.ts +++ b/gitnexus/src/core/ingestion/languages/typescript/scope-resolver.ts @@ -322,6 +322,26 @@ function hasLocalBindingInScopeChain( return true; } + // Type-annotated function parameters — and other value-space type facts + // such as `self` and variable annotations — live in `scope.typeBindings`, + // not `scope.bindings`. A parameter named like a composable genuinely + // shadows the auto-import, and typeBindings never holds a pure type that + // belongs to callable space, so a same-file presence check here cannot + // over-suppress a legitimate auto-import. + // + // Residual (known limitation): this catches parameters whose annotation the + // TS scope query records as a type-binding (`p: Named`, generics, unions, + // predefined, arrays). Function-typed params (`p: () => void`), untyped + // params, destructured locals (`const { x } = …`), and catch-clause vars + // are captured by NEITHER map — the scope query emits no `@declaration` / + // `@type-binding` for them — so those shadow forms still leak an edge. + // Closing that needs shared TS scope-query/extractor changes that alter call + // resolution beyond Nuxt, so it is deferred to a follow-up rather than fixed + // here. + if (scope.filePath === filePath && scope.typeBindings.has(name)) { + return true; + } + cursor = scope.parent; } diff --git a/gitnexus/test/fixtures/lang-resolution/nuxt-auto-imports/pages/param-typed.ts b/gitnexus/test/fixtures/lang-resolution/nuxt-auto-imports/pages/param-typed.ts new file mode 100644 index 000000000..f84d8fd10 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/nuxt-auto-imports/pages/param-typed.ts @@ -0,0 +1,3 @@ +export function renderTyped(validate: ValidateFn) { + validate(); +} diff --git a/gitnexus/test/integration/resolvers/nuxt-scope.test.ts b/gitnexus/test/integration/resolvers/nuxt-scope.test.ts index ad86d83b8..518a638f8 100644 --- a/gitnexus/test/integration/resolvers/nuxt-scope.test.ts +++ b/gitnexus/test/integration/resolvers/nuxt-scope.test.ts @@ -103,11 +103,24 @@ describe('Nuxt/Nitro auto-import scope resolution', () => { it('does not emit auto-import edges for local shadowing or lexical noise', () => { const calls = nuxtCalls(); + // Guard against a vacuous pass: the feature must have emitted edges elsewhere. + expect(calls.length).toBeGreaterThan(0); expect(calls.filter((edge) => edge.sourceFilePath.endsWith('pages/local.ts'))).toHaveLength(0); expect(calls.filter((edge) => edge.sourceFilePath.endsWith('pages/noise.ts'))).toHaveLength(0); }); + it('does not emit an auto-import edge when a typed parameter shadows the name', () => { + const calls = nuxtCalls(); + expect(calls.length).toBeGreaterThan(0); + // pages/param-typed.ts has `function renderTyped(validate: ValidateFn)` and + // calls validate() — the type-annotated parameter (in scope.typeBindings) + // shadows the composable, so no nuxt edge is emitted. + expect( + calls.filter((edge) => edge.sourceFilePath.endsWith('pages/param-typed.ts')), + ).toHaveLength(0); + }); + it('allows type-only local declarations to coexist with value auto-import calls', () => { const calls = nuxtCalls();