* Resolve a Python qualified reference whose receiver is an imported MODULE: * `certs.where()` after `from . import certs`, `mod.func()` after `import mod` * or `from pkg import mod`. The receiver names a submodule (a file), not a * symbol, so the generic symbol lookup in `resolveViaImport` can't
( ref: UnresolvedRef, imports: ImportMapping[], context: ResolutionContext )
| 1346 | * to the other strategies untouched. |
| 1347 | */ |
| 1348 | function resolvePythonModuleMember( |
| 1349 | ref: UnresolvedRef, |
| 1350 | imports: ImportMapping[], |
| 1351 | context: ResolutionContext |
| 1352 | ): ResolvedRef | null { |
| 1353 | const dotIdx = ref.referenceName.indexOf('.'); |
| 1354 | if (dotIdx <= 0) return null; |
| 1355 | const receiver = ref.referenceName.substring(0, dotIdx); |
| 1356 | // The immediate member of the module (first segment after the receiver). |
| 1357 | const member = ref.referenceName.substring(dotIdx + 1).split('.')[0]; |
| 1358 | if (!member) return null; |
| 1359 | |
| 1360 | for (const imp of imports) { |
| 1361 | if (imp.localName !== receiver) continue; |
| 1362 | |
| 1363 | // `import mod` / `import numpy as np` bind the module at `source` itself; |
| 1364 | // `from . import certs` / `from pkg import mod` bind a SUBMODULE whose |
| 1365 | // dotted path is the source joined with the imported name. |
| 1366 | const modulePath = imp.isNamespace |
| 1367 | ? imp.source |
| 1368 | : imp.source.endsWith('.') |
| 1369 | ? imp.source + imp.localName |
| 1370 | : imp.source + '.' + imp.localName; |
| 1371 | |
| 1372 | // resolveImportPath only maps RELATIVE dotted paths (`.mod`, `..pkg.mod`); an |
| 1373 | // ABSOLUTE package path (`pkg.module` from `from pkg import module`, or a bare |
| 1374 | // `import pkg.mod`) resolves to null there, so fall back to the dotted-module |
| 1375 | // file lookup — the same asymmetry resolveModuleImportToFile already handles |
| 1376 | // for the file→file import edge. Without this, a `module.func()` call after |
| 1377 | // `from pkg import module` dropped its `calls` edge even though the import |
| 1378 | // edge resolved (#578). |
| 1379 | let resolvedPath = resolveImportPath(modulePath, ref.filePath, ref.language, context); |
| 1380 | if (!resolvedPath) { |
| 1381 | resolvedPath = findPythonModuleFile(modulePath, context, ref.filePath)?.filePath ?? null; |
| 1382 | } |
| 1383 | if (!resolvedPath || resolvedPath === ref.filePath) continue; |
| 1384 | |
| 1385 | // Find the member as a top-level definition in the module file. Exclude |
| 1386 | // `method` so `mod.foo` never lands on a same-named class method. |
| 1387 | const target = context.getNodesInFile(resolvedPath).find( |
| 1388 | (n) => |
| 1389 | n.name === member && |
| 1390 | (n.kind === 'function' || |
| 1391 | n.kind === 'class' || |
| 1392 | n.kind === 'variable' || |
| 1393 | n.kind === 'constant') |
| 1394 | ); |
| 1395 | if (target) { |
| 1396 | return { original: ref, targetNodeId: target.id, confidence: 0.85, resolvedBy: 'import' }; |
| 1397 | } |
| 1398 | } |
| 1399 | return null; |
| 1400 | } |
| 1401 | |
| 1402 | /** |
| 1403 | * Resolve a whole-MODULE import to that module's file (a file→file dependency). |
no test coverage detected