From 6b24d54a12c105b6b02c4a327e22de677e01f78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=8E=A0=E8=93=9D?= <469906100@qq.com> Date: Tue, 26 May 2026 11:27:35 +0800 Subject: [PATCH 1/2] fix(extract-import-map): resolve tsconfig paths with "./" prefix (#214) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When tsconfig.json uses `"@/*": ["./*"]` (the default in Next.js, Vite, Nuxt), applyTsAlias produces paths like "./utils". The ternary at line 433 skipped posix.join when tsConfigDir was empty, leaving the "./" prefix intact. Since fileSet stores paths without "./" (e.g. "utils.ts"), the probe always missed — dropping all alias-based import edges. Fix: always run posix.join(tsConfigDir, relativeToConfig). When tsConfigDir is '', posix.join('', './utils') normalizes to 'utils', matching fileSet. Added 3 test cases covering "./*", "./src/*", and multi-target fallback with "./" prefixes. --- .../test_extract_import_map.test.mjs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/skill/understand/test_extract_import_map.test.mjs b/tests/skill/understand/test_extract_import_map.test.mjs index b5f132ef..edb64b5a 100644 --- a/tests/skill/understand/test_extract_import_map.test.mjs +++ b/tests/skill/understand/test_extract_import_map.test.mjs @@ -1596,6 +1596,88 @@ describe('extract-import-map.mjs — tree-sitter init graceful failure', () => { expect(result.output.stats.filesWithImports).toBe(0); expect(result.output.stats.totalEdges).toBe(0); }); + + it('resolves tsconfig paths with "./" prefix in targets (Next.js/Vite default)', () => { + projectRoot = setupTree({ + 'tsconfig.json': JSON.stringify({ + compilerOptions: { + baseUrl: '.', + paths: { + '@/*': ['./*'], + }, + }, + }), + 'app/page.tsx': `import { cn } from '@/lib/utils';\ncn();\n`, + 'lib/utils.ts': `export function cn(...args: string[]) { return args.join(' '); }\n`, + }); + + const result = runScript(projectRoot, { + projectRoot, + files: [ + { path: 'app/page.tsx', language: 'typescript', fileCategory: 'code' }, + { path: 'lib/utils.ts', language: 'typescript', fileCategory: 'code' }, + ], + }); + + expect(result.status).toBe(0); + expect(result.output.scriptCompleted).toBe(true); + expect(result.output.importMap['app/page.tsx']).toEqual(['lib/utils.ts']); + }); + + it('resolves tsconfig paths with "./src/*" prefix in targets', () => { + projectRoot = setupTree({ + 'tsconfig.json': JSON.stringify({ + compilerOptions: { + baseUrl: '.', + paths: { + '@/*': ['./src/*'], + }, + }, + }), + 'src/index.ts': `import { Button } from '@/components/Button';\n`, + 'src/components/Button.tsx': `export function Button() { return null; }\n`, + }); + + const result = runScript(projectRoot, { + projectRoot, + files: [ + { path: 'src/index.ts', language: 'typescript', fileCategory: 'code' }, + { path: 'src/components/Button.tsx', language: 'typescript', fileCategory: 'code' }, + ], + }); + + expect(result.status).toBe(0); + expect(result.output.scriptCompleted).toBe(true); + expect(result.output.importMap['src/index.ts']).toEqual(['src/components/Button.tsx']); + }); + + it('resolves tsconfig paths with multiple "./"-prefixed targets (fallback)', () => { + projectRoot = setupTree({ + 'tsconfig.json': JSON.stringify({ + compilerOptions: { + baseUrl: '.', + paths: { + '~/*': ['./src/*', './shared/*'], + }, + }, + }), + 'src/index.ts': `import { helper } from '~/helper';\n`, + 'shared/helper.ts': `export function helper() {}\n`, + }); + + const result = runScript(projectRoot, { + projectRoot, + files: [ + { path: 'src/index.ts', language: 'typescript', fileCategory: 'code' }, + { path: 'shared/helper.ts', language: 'typescript', fileCategory: 'code' }, + ], + }); + + expect(result.status).toBe(0); + expect(result.output.scriptCompleted).toBe(true); + // helper.ts only exists in shared/, so the second target should match + expect(result.output.importMap['src/index.ts']).toEqual(['shared/helper.ts']); + }); }); describe('extract-import-map.mjs — deterministic stderr ordering across loaders', () => { From cef7e012b0d977b93e07e1fc1f730f4bd3d934d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=8E=A0=E8=93=9D?= <469906100@qq.com> Date: Tue, 9 Jun 2026 13:36:45 +0800 Subject: [PATCH 2/2] test(extract-import-map): drop duplicate #214 tests after rebase Latest main already carries the #214 resolver fix and focused regression coverage. After rebasing, the original PR tests were duplicated in the tree-sitter failure block and no longer formed a useful diff. Drop the duplicate block so the PR branch matches the resolved main behavior. --- .../test_extract_import_map.test.mjs | 82 ------------------- 1 file changed, 82 deletions(-) diff --git a/tests/skill/understand/test_extract_import_map.test.mjs b/tests/skill/understand/test_extract_import_map.test.mjs index edb64b5a..b5f132ef 100644 --- a/tests/skill/understand/test_extract_import_map.test.mjs +++ b/tests/skill/understand/test_extract_import_map.test.mjs @@ -1596,88 +1596,6 @@ describe('extract-import-map.mjs — tree-sitter init graceful failure', () => { expect(result.output.stats.filesWithImports).toBe(0); expect(result.output.stats.totalEdges).toBe(0); }); - - it('resolves tsconfig paths with "./" prefix in targets (Next.js/Vite default)', () => { - projectRoot = setupTree({ - 'tsconfig.json': JSON.stringify({ - compilerOptions: { - baseUrl: '.', - paths: { - '@/*': ['./*'], - }, - }, - }), - 'app/page.tsx': `import { cn } from '@/lib/utils';\ncn();\n`, - 'lib/utils.ts': `export function cn(...args: string[]) { return args.join(' '); }\n`, - }); - - const result = runScript(projectRoot, { - projectRoot, - files: [ - { path: 'app/page.tsx', language: 'typescript', fileCategory: 'code' }, - { path: 'lib/utils.ts', language: 'typescript', fileCategory: 'code' }, - ], - }); - - expect(result.status).toBe(0); - expect(result.output.scriptCompleted).toBe(true); - expect(result.output.importMap['app/page.tsx']).toEqual(['lib/utils.ts']); - }); - - it('resolves tsconfig paths with "./src/*" prefix in targets', () => { - projectRoot = setupTree({ - 'tsconfig.json': JSON.stringify({ - compilerOptions: { - baseUrl: '.', - paths: { - '@/*': ['./src/*'], - }, - }, - }), - 'src/index.ts': `import { Button } from '@/components/Button';\n`, - 'src/components/Button.tsx': `export function Button() { return null; }\n`, - }); - - const result = runScript(projectRoot, { - projectRoot, - files: [ - { path: 'src/index.ts', language: 'typescript', fileCategory: 'code' }, - { path: 'src/components/Button.tsx', language: 'typescript', fileCategory: 'code' }, - ], - }); - - expect(result.status).toBe(0); - expect(result.output.scriptCompleted).toBe(true); - expect(result.output.importMap['src/index.ts']).toEqual(['src/components/Button.tsx']); - }); - - it('resolves tsconfig paths with multiple "./"-prefixed targets (fallback)', () => { - projectRoot = setupTree({ - 'tsconfig.json': JSON.stringify({ - compilerOptions: { - baseUrl: '.', - paths: { - '~/*': ['./src/*', './shared/*'], - }, - }, - }), - 'src/index.ts': `import { helper } from '~/helper';\n`, - 'shared/helper.ts': `export function helper() {}\n`, - }); - - const result = runScript(projectRoot, { - projectRoot, - files: [ - { path: 'src/index.ts', language: 'typescript', fileCategory: 'code' }, - { path: 'shared/helper.ts', language: 'typescript', fileCategory: 'code' }, - ], - }); - - expect(result.status).toBe(0); - expect(result.output.scriptCompleted).toBe(true); - // helper.ts only exists in shared/, so the second target should match - expect(result.output.importMap['src/index.ts']).toEqual(['shared/helper.ts']); - }); }); describe('extract-import-map.mjs — deterministic stderr ordering across loaders', () => {