* Resolve a relative import
( importPath: string, fromDir: string, language: Language, context: ResolutionContext )
| 380 | * Resolve a relative import |
| 381 | */ |
| 382 | function resolveRelativeImport( |
| 383 | importPath: string, |
| 384 | fromDir: string, |
| 385 | language: Language, |
| 386 | context: ResolutionContext |
| 387 | ): string | null { |
| 388 | const projectRoot = context.getProjectRoot(); |
| 389 | const extensions = EXTENSION_RESOLUTION[language] || []; |
| 390 | |
| 391 | // Python dotted-relative imports (`from .certs import x`, `from ..pkg.mod |
| 392 | // import y`): leading dots are PACKAGE levels (1 = current package), and the |
| 393 | // remainder is a dotted submodule path. `path.resolve(dir, '.certs')` would |
| 394 | // treat `.certs` as a literal hidden filename, so translate the Python form |
| 395 | // to a real filesystem-relative path before resolving. |
| 396 | if (language === 'python' && importPath.startsWith('.')) { |
| 397 | const dots = importPath.length - importPath.replace(/^\.+/, '').length; |
| 398 | const up = '../'.repeat(Math.max(0, dots - 1)); // 1 dot = current dir |
| 399 | const rest = importPath.slice(dots).replace(/\./g, '/'); // 'sub.mod' -> 'sub/mod' |
| 400 | const pyBase = path.resolve(fromDir, up + rest); |
| 401 | const pyRel = path.relative(projectRoot, pyBase).replace(/\\/g, '/'); |
| 402 | for (const ext of extensions) { |
| 403 | if (context.fileExists(pyRel + ext)) return pyRel + ext; |
| 404 | } |
| 405 | if (pyRel && context.fileExists(pyRel)) return pyRel; |
| 406 | return null; |
| 407 | } |
| 408 | |
| 409 | // Try the path as-is first |
| 410 | const basePath = path.resolve(fromDir, importPath); |
| 411 | const relativePath = path.relative(projectRoot, basePath).replace(/\\/g, '/'); |
| 412 | |
| 413 | // Try each extension |
| 414 | for (const ext of extensions) { |
| 415 | const candidatePath = relativePath + ext; |
| 416 | if (context.fileExists(candidatePath)) { |
| 417 | return candidatePath; |
| 418 | } |
| 419 | } |
| 420 | |
| 421 | // Try without extension (might already have one) |
| 422 | if (context.fileExists(relativePath)) { |
| 423 | return relativePath; |
| 424 | } |
| 425 | |
| 426 | return null; |
| 427 | } |
| 428 | |
| 429 | /** |
| 430 | * Resolve an aliased/absolute import. |
no test coverage detected