mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* feat: ✨ resolve Nuxt/Nitro auto-imports in TypeScript scope resolver * fix: 🐛 skip self-referential edges in Nuxt auto-import emission * fix: 🐛 address Sourcery review -- gate Nitro scan on imports.d.ts and pre-index explicit imports * fix: scope Nuxt auto-import resolution * fix: address Nuxt auto-import review follow-ups * fix(ingestion): capture only LHS binding names in Nitro server-util exports The Nuxt server-util export scanner ran a declarator regex over the whole `export const …` right-hand side, so it registered RHS tokens as auto-import names: arrow-function parameters (`export const f = (event) => …` → `event`), object-literal keys (`export const c = { onError } ` → `onError`), and bare operands. It also dropped generic-typed declarators (`export const x: Map<a, b> = …`) because the type-annotation skip broke at the comma inside the generic. Both produced wrong/missing auto-import CALLS edges. Capture only the leading binding name of each top-level declarator via a depth-aware comma splitter (tracks (), [], {}, <>), skipping destructuring patterns. Nitro auto-imports only surface top-level binding names, so the RHS is never parsed. Adds unit coverage for the param/object-key/operand/generic and multi-declarator forms. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4W25WLfYD1JNy8icxeLPU * fix(ingestion): stop Nitro server callers resolving client composables `getNuxtAutoImportEntry` fell back to the client composable map when a `server/api|routes|middleware` caller's name had no `server/utils` entry. But Nitro only auto-imports `server/utils/**` into the server context — app `composables/` are Vue-app-only — so that fallback minted CALLS/IMPORTS edges Nitro never creates (e.g. a server route "calling" a composable it cannot see without an explicit import). Server callers now resolve the server map only. Restructure the barrel-directory integration test to use a client caller (which legitimately auto-imports the composable) so `index.*` resolution stays covered, and add a negative assertion that `server/api/route.ts` emits no edge to `composables/*` while its real `server/utils` call still resolves. Unit test locks that a server caller does not fall back to a client-only name. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4W25WLfYD1JNy8icxeLPU * fix(ingestion): let unresolved explicit imports shadow Nuxt auto-imports The explicit-import suppression index only recorded import local names whose edge resolved to a file (`edge.targetFile !== null`). An explicit import from an unresolved external package — `import { useAuto } from '@vueuse/core'; useAuto()` — therefore escaped suppression, and the post-resolution hook emitted a spurious Nuxt auto-import CALLS edge for a name the file already imports explicitly. Record the local name regardless of whether the import resolved: an explicit import is authoritative shadowing intent. Adds an integration fixture importing from an external package and a (non-vacuous) assertion that it emits no nuxt edge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4W25WLfYD1JNy8icxeLPU * fix(ingestion): let type-annotated params shadow Nuxt auto-imports 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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4W25WLfYD1JNy8icxeLPU * fix(ingestion): treat server/plugins and server/tasks as Nitro runtime isNitroServerRuntimeFile only matched server/api, server/routes, and server/middleware. Nitro also auto-imports server/utils into server/plugins and (since Nitro 2.6) server/tasks, so callers there were misrouted to the client composable map. Extend the prefix set (now a named constant) to cover them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4W25WLfYD1JNy8icxeLPU * fix(ingestion): merge duplicate JSDoc on collectImportsDts Two consecutive JSDoc blocks preceded collectImportsDts; tooling (IDEs, TypeDoc) attaches only the last one, silently dropping the descriptive block. Fold the "returns true when read" line into the descriptive block as a `@returns` tag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4W25WLfYD1JNy8icxeLPU * fix(ingestion): contain .nuxt/imports.d.ts source resolution to the repo A crafted `.nuxt/imports.d.ts` source such as `from '../../../../etc/passwd'` passes the project-local relative-path check but resolves outside the analyzed repo, causing fs.stat probes against arbitrary host paths. Skip any source that resolves outside repoRoot before touching the filesystem. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4W25WLfYD1JNy8icxeLPU --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
164 lines
5.8 KiB
TypeScript
164 lines
5.8 KiB
TypeScript
import { describe, it, expect, beforeAll } from 'vitest';
|
|
import path from 'path';
|
|
import { FIXTURES, getRelationships, runPipelineFromRepo, type PipelineResult } from './helpers.js';
|
|
|
|
describe('Nuxt/Nitro auto-import scope resolution', () => {
|
|
let result: PipelineResult;
|
|
|
|
beforeAll(async () => {
|
|
result = await runPipelineFromRepo(path.join(FIXTURES, 'nuxt-auto-imports'), () => {}, {
|
|
skipGraphPhases: true,
|
|
});
|
|
}, 60000);
|
|
|
|
function nuxtCalls() {
|
|
return getRelationships(result, 'CALLS').filter(
|
|
(edge) => edge.rel.reason === 'nuxt-auto-import',
|
|
);
|
|
}
|
|
|
|
it('prefers server/utils over client composables for same-named server calls', () => {
|
|
const calls = nuxtCalls();
|
|
const serverValidate = calls.find(
|
|
(edge) =>
|
|
edge.sourceFilePath.endsWith('server/api/route.ts') &&
|
|
edge.target === 'validate' &&
|
|
edge.targetFilePath.endsWith('server/utils/serverValidate.ts'),
|
|
);
|
|
const wrongClientValidate = calls.find(
|
|
(edge) =>
|
|
edge.sourceFilePath.endsWith('server/api/route.ts') &&
|
|
edge.target === 'validate' &&
|
|
edge.targetFilePath.endsWith('composables/clientValidate.ts'),
|
|
);
|
|
|
|
expect(serverValidate).toBeDefined();
|
|
expect(wrongClientValidate).toBeUndefined();
|
|
});
|
|
|
|
it('keeps server/utils out of client files while allowing client auto-imports', () => {
|
|
const calls = nuxtCalls();
|
|
expect(
|
|
calls.find(
|
|
(edge) =>
|
|
edge.sourceFilePath.endsWith('app.ts') &&
|
|
edge.target === 'validate' &&
|
|
edge.targetFilePath.endsWith('composables/clientValidate.ts'),
|
|
),
|
|
).toBeDefined();
|
|
expect(
|
|
calls.find(
|
|
(edge) =>
|
|
edge.sourceFilePath.endsWith('app.ts') &&
|
|
edge.targetFilePath.endsWith('server/utils/serverOnly.ts'),
|
|
),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
it('resolves extensionless barrel directories to index files', () => {
|
|
const calls = nuxtCalls();
|
|
const imports = getRelationships(result, 'IMPORTS').filter(
|
|
(edge) => edge.rel.reason === 'nuxt-auto-import-file',
|
|
);
|
|
|
|
expect(
|
|
calls.find(
|
|
(edge) =>
|
|
edge.sourceFilePath.endsWith('app.ts') &&
|
|
edge.target === 'useBarrel' &&
|
|
edge.targetFilePath.endsWith('composables/group/index.ts'),
|
|
),
|
|
).toBeDefined();
|
|
expect(
|
|
imports.find(
|
|
(edge) =>
|
|
edge.sourceFilePath.endsWith('app.ts') &&
|
|
edge.targetFilePath.endsWith('composables/group/index.ts'),
|
|
),
|
|
).toBeDefined();
|
|
});
|
|
|
|
it('does not resolve client composables from Nitro server callers (no client fallback)', () => {
|
|
const calls = nuxtCalls();
|
|
// server/api/route.ts calls validate() (a real server/util), useAuto() and
|
|
// useBarrel() (client-only composables). Only the server/util resolves;
|
|
// Nitro does not auto-import composables/ server-side, so no edge is emitted
|
|
// to either composable.
|
|
const composableEdges = calls.filter(
|
|
(edge) =>
|
|
edge.sourceFilePath.endsWith('server/api/route.ts') &&
|
|
edge.targetFilePath.includes('/composables/'),
|
|
);
|
|
expect(composableEdges).toHaveLength(0);
|
|
// The legitimate server/util edge still resolves.
|
|
expect(
|
|
calls.find(
|
|
(edge) =>
|
|
edge.sourceFilePath.endsWith('server/api/route.ts') &&
|
|
edge.target === 'validate' &&
|
|
edge.targetFilePath.endsWith('server/utils/serverValidate.ts'),
|
|
),
|
|
).toBeDefined();
|
|
});
|
|
|
|
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();
|
|
|
|
expect(
|
|
calls.find(
|
|
(edge) =>
|
|
edge.sourceFilePath.endsWith('pages/type-only.ts') &&
|
|
edge.target === 'useAuto' &&
|
|
edge.targetFilePath.endsWith('composables/useAuto.ts'),
|
|
),
|
|
).toBeDefined();
|
|
});
|
|
|
|
it('suppresses an auto-import shadowed by an explicit unresolved external import', () => {
|
|
const calls = nuxtCalls();
|
|
// Guard against a vacuous pass: the feature must have emitted edges elsewhere.
|
|
expect(calls.length).toBeGreaterThan(0);
|
|
// pages/external-import.ts does `import { useAuto } from '@vueuse/core'` (an
|
|
// unresolved external) then calls useAuto(). The explicit import shadows the
|
|
// Nuxt auto-import, so no nuxt edge is emitted from that file.
|
|
expect(
|
|
calls.filter((edge) => edge.sourceFilePath.endsWith('pages/external-import.ts')),
|
|
).toHaveLength(0);
|
|
});
|
|
|
|
it('suppresses only explicitly imported local names, not every symbol from the same source', () => {
|
|
const calls = nuxtCalls();
|
|
|
|
expect(
|
|
calls.find(
|
|
(edge) => edge.sourceFilePath.endsWith('pages/explicit.ts') && edge.target === 'useAuto',
|
|
),
|
|
).toBeDefined();
|
|
expect(
|
|
calls.find(
|
|
(edge) =>
|
|
edge.sourceFilePath.endsWith('pages/explicit-auto.ts') && edge.target === 'useAuto',
|
|
),
|
|
).toBeUndefined();
|
|
});
|
|
});
|