(node, source: bytes, file_nid: str, stem: str, edges: list, str_path: str, scope_stack: list[str] | None = None)
| 1852 | |
| 1853 | |
| 1854 | def _import_js(node, source: bytes, file_nid: str, stem: str, edges: list, str_path: str, scope_stack: list[str] | None = None) -> None: |
| 1855 | is_reexport = node.type == "export_statement" |
| 1856 | # Only handle export_statement if it has a `from` clause (re-export). |
| 1857 | # Pure exports like `export const x = 1` or `export { localVar }` have no source module. |
| 1858 | if is_reexport: |
| 1859 | has_from = any(child.type == "from" or (_read_text(child, source) == "from") for child in node.children if child.type in ("from", "identifier")) |
| 1860 | if not has_from: |
| 1861 | # Check for string child (source path) as a more reliable indicator |
| 1862 | has_from = any(child.type == "string" for child in node.children) |
| 1863 | if not has_from: |
| 1864 | return |
| 1865 | |
| 1866 | resolved_path: "Path | None" = None |
| 1867 | module_string = None |
| 1868 | for child in node.children: |
| 1869 | if child.type == "string": |
| 1870 | module_string = child |
| 1871 | break |
| 1872 | if child.type == "import_require_clause": |
| 1873 | # TS import-equals form: `import x = require("./m")`. The module |
| 1874 | # string sits inside the clause, not on the import_statement |
| 1875 | # itself, so the direct-child scan above never sees it. |
| 1876 | module_string = next( |
| 1877 | (sub for sub in child.children if sub.type == "string"), None |
| 1878 | ) |
| 1879 | break |
| 1880 | if module_string is not None: |
| 1881 | raw = _read_text(module_string, source).strip("'\"` ") |
| 1882 | resolved = _resolve_js_import_target(raw, str_path) |
| 1883 | if resolved is not None: |
| 1884 | tgt_nid, resolved_path = resolved |
| 1885 | edges.append({ |
| 1886 | "source": file_nid, |
| 1887 | "target": tgt_nid, |
| 1888 | "relation": "imports_from", |
| 1889 | "context": "re-export" if is_reexport else "import", |
| 1890 | "confidence": "EXTRACTED", |
| 1891 | "source_file": str_path, |
| 1892 | "source_location": f"L{node.start_point[0] + 1}", |
| 1893 | "weight": 1.0, |
| 1894 | }) |
| 1895 | |
| 1896 | # Emit symbol-level edges for named imports/re-exports from local/aliased files. |
| 1897 | # e.g. `import { Foo, type Bar } from './bar'` → file → Foo, file → Bar (EXTRACTED) |
| 1898 | # e.g. `export { Foo } from './bar'` → file → Foo (re_exports edge) |
| 1899 | # Uses the same _make_id(target_stem, name) key that _extract_generic emits when |
| 1900 | # defining the symbol, so these edges wire importers directly to existing symbol nodes. |
| 1901 | if resolved_path is not None: |
| 1902 | target_stem = _file_stem(resolved_path) |
| 1903 | line = node.start_point[0] + 1 |
| 1904 | |
| 1905 | if is_reexport: |
| 1906 | # Handle: export { foo, bar } from './module' |
| 1907 | # export { default as baz } from './module' |
| 1908 | for child in node.children: |
| 1909 | if child.type == "export_clause": |
| 1910 | for spec in child.children: |
| 1911 | if spec.type == "export_specifier": |
nothing calls this directly
no test coverage detected