* 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 )
| 1605 | * to the other strategies untouched. |
| 1606 | */ |
| 1607 | function resolvePythonModuleMember( |
| 1608 | ref: UnresolvedRef, |
| 1609 | imports: ImportMapping[], |
| 1610 | context: ResolutionContext |
| 1611 | ): ResolvedRef | null { |
| 1612 | const dotIdx = ref.referenceName.indexOf('.'); |
| 1613 | if (dotIdx <= 0) return null; |
| 1614 | const receiver = ref.referenceName.substring(0, dotIdx); |
| 1615 | // The immediate member of the module (first segment after the receiver). |
| 1616 | const member = ref.referenceName.substring(dotIdx + 1).split('.')[0]; |
| 1617 | if (!member) return null; |
| 1618 | |
| 1619 | for (const imp of imports) { |
| 1620 | if (imp.localName !== receiver) continue; |
| 1621 | |
| 1622 | // `import mod` / `import numpy as np` bind the module at `source` itself; |
| 1623 | // `from . import certs` / `from pkg import mod` bind a SUBMODULE whose |
| 1624 | // dotted path is the source joined with the imported name. |
| 1625 | const modulePath = imp.isNamespace |
| 1626 | ? imp.source |
| 1627 | : imp.source.endsWith('.') |
| 1628 | ? imp.source + imp.localName |
| 1629 | : imp.source + '.' + imp.localName; |
| 1630 | |
| 1631 | // resolveImportPath only maps RELATIVE dotted paths (`.mod`, `..pkg.mod`); an |
| 1632 | // ABSOLUTE package path (`pkg.module` from `from pkg import module`, or a bare |
| 1633 | // `import pkg.mod`) resolves to null there, so fall back to the dotted-module |
| 1634 | // file lookup — the same asymmetry resolveModuleImportToFile already handles |
| 1635 | // for the file→file import edge. Without this, a `module.func()` call after |
| 1636 | // `from pkg import module` dropped its `calls` edge even though the import |
| 1637 | // edge resolved (#578). |
| 1638 | let resolvedPath = resolveImportPath(modulePath, ref.filePath, ref.language, context); |
| 1639 | if (!resolvedPath) { |
| 1640 | resolvedPath = findPythonModuleFile(modulePath, context, ref.filePath)?.filePath ?? null; |
| 1641 | } |
| 1642 | if (!resolvedPath || resolvedPath === ref.filePath) continue; |
| 1643 | |
| 1644 | // Find the member as a top-level definition in the module file. Exclude |
| 1645 | // `method` so `mod.foo` never lands on a same-named class method. |
| 1646 | const target = context.getNodesInFile(resolvedPath).find( |
| 1647 | (n) => |
| 1648 | n.name === member && |
| 1649 | (n.kind === 'function' || |
| 1650 | n.kind === 'class' || |
| 1651 | n.kind === 'variable' || |
| 1652 | n.kind === 'constant') |
| 1653 | ); |
| 1654 | if (target) { |
| 1655 | return { original: ref, targetNodeId: target.id, confidence: 0.85, resolvedBy: 'import' }; |
| 1656 | } |
| 1657 | } |
| 1658 | return null; |
| 1659 | } |
| 1660 | |
| 1661 | /** |
| 1662 | * Resolve a whole-MODULE import to that module's file (a file→file dependency). |
no test coverage detected