GitNexus/gitnexus/test/unit/python-relative-imports.test.ts
Gergő Magyar d57f570dff
refactor(ingestion): colocate import resolution rules (#3670)
* refactor(ingestion): remove dormant import resolvers

Remove the unused LanguageProvider importResolver contract, factory and strategy chain. Keep active scope resolver helpers and their semantic tests.

Verified 5,309 corpus edges unchanged, 7,411 focused tests, typechecks, build and resolver benchmarks. Full local suite is still running; graph analysis remains partial on deleted and comment-only regions.

* refactor: colocate language import helpers

Keep import policy beside language resolvers and share only generic
indexes. Pass the existing ordered candidates explicitly so lookup
behavior and cache identities stay unchanged.

* fix: defer MCP imports and narrow Python paths

* fix(python): prefer packages in relative imports

* test(python): pin package-first import lookup

* test(python): use a valid parent-relative import

---------

Co-authored-by: Gergely Magyar <magyargergo@users.noreply.github.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
2026-10-22 03:49:35 +00:00

52 lines
2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { resolvePythonImportInternal } from '../../src/core/ingestion/languages/python/import-helpers.js';
import { resolvePythonImportTarget } from '../../src/core/ingestion/languages/python/import-target.js';
/** Both the helper and the public adapter must respect Python's file boundary. */
function resolveBoth(files: string[], target = '.peer', fromFile = 'pkg/main.py') {
const allFilePaths = new Set([fromFile, ...files]);
return [
resolvePythonImportInternal(fromFile, target, allFilePaths),
resolvePythonImportTarget(
{ kind: 'namespace', targetRaw: target, localName: 'peer', importedName: '*' },
{ fromFile, allFilePaths },
),
];
}
describe('Python relative import file candidates', () => {
it.each(['.ts', '.tsx', '.js', '.php', '.rb', ''])(
'leaves a relative import unresolved when only peer%s exists',
(extension) => {
expect(resolveBoth([`pkg/peer${extension}`])).toEqual([null, null]);
},
);
it('selects the Python module despite a same-named TypeScript file', () => {
expect(resolveBoth(['pkg/peer.ts', 'pkg/peer.py'])).toEqual(['pkg/peer.py', 'pkg/peer.py']);
});
it('selects a Python package despite a same-named TypeScript file', () => {
expect(resolveBoth(['pkg/peer.ts', 'pkg/peer/__init__.py'])).toEqual([
'pkg/peer/__init__.py',
'pkg/peer/__init__.py',
]);
});
it.each([
['.peer', 'pkg/main.py'],
['..peer', 'pkg/child/main.py'],
])('prefers a package over a module for %s from %s', (target, fromFile) => {
expect(resolveBoth(['pkg/peer.py', 'pkg/peer/__init__.py'], target, fromFile)).toEqual([
'pkg/peer/__init__.py',
'pkg/peer/__init__.py',
]);
});
it('resolves a parent-relative Python module without borrowing a TypeScript file', () => {
expect(resolveBoth(['pkg/peer.ts', 'pkg/peer.py'], '..peer', 'pkg/child/main.py')).toEqual([
'pkg/peer.py',
'pkg/peer.py',
]);
});
});