Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions crates/codegraph-core/src/domain/graph/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1503,13 +1503,17 @@ fn probe_known_extensions(
const EXTENSIONS: &[&str] = &[
".ts",
".tsx",
".mts",
".cts",
".js",
".jsx",
".mjs",
".py",
".pyi",
"/index.ts",
"/index.tsx",
"/index.mts",
"/index.cts",
"/index.js",
"/__init__.py",
];
Expand Down Expand Up @@ -1950,6 +1954,97 @@ mod tests {
assert_eq!(result, "src/legacy.cts");
}

#[test]
fn probe_known_extensions_resolves_an_extension_less_specifier_to_mts() {
// #2464: distinct from resolve_mjs_to_mts_remap_with_known_files above
// — `./utils` here carries no extension at all, unlike `./utils.mjs`,
// so it exercises probe_known_extensions's EXTENSIONS list directly
// instead of EMIT_EXTENSION_REMAPS.
let mut known = HashSet::new();
known.insert("src/utils.mts".to_string());

let aliases = PathAliases {
base_url: None,
paths: vec![],
};

let result = resolve_import_path_inner(
"/project/src/index.mts",
"./utils",
"/project",
&aliases,
Some(&known),
None,
);
assert_eq!(result, "src/utils.mts");
}

#[test]
fn probe_known_extensions_resolves_an_extension_less_specifier_to_cts() {
let mut known = HashSet::new();
known.insert("src/legacy.cts".to_string());

let aliases = PathAliases {
base_url: None,
paths: vec![],
};

let result = resolve_import_path_inner(
"/project/src/index.cts",
"./legacy",
"/project",
&aliases,
Some(&known),
None,
);
assert_eq!(result, "src/legacy.cts");
}

#[test]
fn probe_known_extensions_resolves_a_directory_specifier_to_its_index_mts() {
// Greptile follow-up on #2464: the direct .mts/.cts candidates alone
// don't cover the directory-index convention (`./dir` -> `dir/index.mts`),
// which every other extension in this list already supports.
let mut known = HashSet::new();
known.insert("src/esm-dir/index.mts".to_string());

let aliases = PathAliases {
base_url: None,
paths: vec![],
};

let result = resolve_import_path_inner(
"/project/src/index.mts",
"./esm-dir",
"/project",
&aliases,
Some(&known),
None,
);
assert_eq!(result, "src/esm-dir/index.mts");
}

#[test]
fn probe_known_extensions_resolves_a_directory_specifier_to_its_index_cts() {
let mut known = HashSet::new();
known.insert("src/cjs-dir/index.cts".to_string());

let aliases = PathAliases {
base_url: None,
paths: vec![],
};

let result = resolve_import_path_inner(
"/project/src/index.cts",
"./cjs-dir",
"/project",
&aliases,
Some(&known),
None,
);
assert_eq!(result, "src/cjs-dir/index.cts");
}

#[test]
fn probe_js_to_ts_remap_does_not_corrupt_a_path_with_an_earlier_js_substring() {
// Regression guard: the previous implementation used
Expand Down
4 changes: 4 additions & 0 deletions src/domain/graph/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1259,13 +1259,17 @@ function resolveImportPathJS(
for (const ext of [
'.ts',
'.tsx',
'.mts',
'.cts',
Comment thread
carlos-alm marked this conversation as resolved.
'.js',
'.jsx',
'.mjs',
'.py',
'.pyi',
'/index.ts',
'/index.tsx',
'/index.mts',
'/index.cts',
'/index.js',
'/__init__.py',
]) {
Expand Down
39 changes: 39 additions & 0 deletions tests/unit/resolve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@ beforeAll(() => {
// shared/core.ts (for alias resolution)
// src/esm/util.mts (for .mjs -> .mts remap, #2299)
// src/cjs/legacy.cts (for .cjs -> .cts remap, #2299)
// src/esm-dir/index.mts (for /index.mts directory resolution, #2464)
// src/cjs-dir/index.cts (for /index.cts directory resolution, #2464)
fs.mkdirSync(path.join(tmpDir, 'src', 'lib'), { recursive: true });
fs.mkdirSync(path.join(tmpDir, 'src', 'esm'), { recursive: true });
fs.mkdirSync(path.join(tmpDir, 'src', 'cjs'), { recursive: true });
fs.mkdirSync(path.join(tmpDir, 'src', 'esm-dir'), { recursive: true });
fs.mkdirSync(path.join(tmpDir, 'src', 'cjs-dir'), { recursive: true });
fs.mkdirSync(path.join(tmpDir, 'shared'), { recursive: true });

fs.writeFileSync(path.join(tmpDir, 'src', 'math.js'), 'export const add = (a, b) => a + b;');
Expand All @@ -65,6 +69,11 @@ beforeAll(() => {
fs.writeFileSync(path.join(tmpDir, 'shared', 'core.ts'), 'export const x = 1;');
fs.writeFileSync(path.join(tmpDir, 'src', 'esm', 'util.mts'), 'export const greet = () => "hi";');
fs.writeFileSync(path.join(tmpDir, 'src', 'cjs', 'legacy.cts'), 'export const x = 1;');
fs.writeFileSync(
path.join(tmpDir, 'src', 'esm-dir', 'index.mts'),
'export const greet = () => "hi";',
);
fs.writeFileSync(path.join(tmpDir, 'src', 'cjs-dir', 'index.cts'), 'export const x = 1;');
});

afterAll(() => {
Expand Down Expand Up @@ -112,6 +121,36 @@ describe('resolveImportPathJS', () => {
expect(result).toMatch(/legacy\.cts$/);
});

it('resolves an extension-less specifier to a .mts file (#2464)', () => {
// Distinct from the #2299 remap tests above: `./util` here carries no
// extension at all, unlike `./util.mjs`, so it exercises the
// extension-probing loop directly instead of EMIT_EXTENSION_REMAPS.
const fromFile = path.join(tmpDir, 'src', 'esm', 'index.mts');
const result = resolveImportPathJS(fromFile, './util', tmpDir, null);
expect(result).toMatch(/util\.mts$/);
});

it('resolves an extension-less specifier to a .cts file (#2464)', () => {
const fromFile = path.join(tmpDir, 'src', 'cjs', 'index.cts');
const result = resolveImportPathJS(fromFile, './legacy', tmpDir, null);
expect(result).toMatch(/legacy\.cts$/);
});

it('resolves a directory specifier to its index.mts file (#2464, Greptile follow-up)', () => {
// Caught by review: the direct .mts/.cts candidates alone don't cover
// the directory-index convention (`import './dir'` -> `dir/index.mts`),
// which every other extension in this list already supports.
const fromFile = path.join(tmpDir, 'src', 'index.mts');
const result = resolveImportPathJS(fromFile, './esm-dir', tmpDir, null);
expect(result).toMatch(/esm-dir\/index\.mts$/);
});

it('resolves a directory specifier to its index.cts file (#2464, Greptile follow-up)', () => {
const fromFile = path.join(tmpDir, 'src', 'index.cts');
const result = resolveImportPathJS(fromFile, './cjs-dir', tmpDir, null);
expect(result).toMatch(/cjs-dir\/index\.cts$/);
});

it('resolves directory to index.js', () => {
const fromFile = path.join(tmpDir, 'src', 'index.js');
const result = resolveImportPathJS(fromFile, './lib', tmpDir, null);
Expand Down
Loading