Detect dynamic import() calls in JS/TS and emit imports_from edges. Handles patterns like: await import('./foo.js') import('./foo.js').then(...) const m = await import(`./foo`) Returns True if the node was a dynamic import (caller should skip normal call handling).
(node, source: bytes, caller_nid: str, str_path: str, edges: list,
seen_dyn_pairs: set)
| 1949 | |
| 1950 | |
| 1951 | def _dynamic_import_js(node, source: bytes, caller_nid: str, str_path: str, edges: list, |
| 1952 | seen_dyn_pairs: set) -> bool: |
| 1953 | """Detect dynamic import() calls in JS/TS and emit imports_from edges. |
| 1954 | |
| 1955 | Handles patterns like: |
| 1956 | await import('./foo.js') |
| 1957 | import('./foo.js').then(...) |
| 1958 | const m = await import(`./foo`) |
| 1959 | |
| 1960 | Returns True if the node was a dynamic import (caller should skip normal call handling). |
| 1961 | """ |
| 1962 | # Dynamic import is a call_expression whose function child is the keyword "import". |
| 1963 | # tree-sitter-typescript parses `import('...')` as call_expression with first child |
| 1964 | # being an "import" token (type="import"). |
| 1965 | func_node = node.child_by_field_name("function") |
| 1966 | if func_node is None: |
| 1967 | # Fallback: check first child directly (some TS versions) |
| 1968 | if node.children and _read_text(node.children[0], source) == "import": |
| 1969 | func_node = node.children[0] |
| 1970 | else: |
| 1971 | return False |
| 1972 | if _read_text(func_node, source) != "import": |
| 1973 | return False |
| 1974 | |
| 1975 | # Extract the module path from the arguments |
| 1976 | args = node.child_by_field_name("arguments") |
| 1977 | if args is None: |
| 1978 | return True # It's an import() but no args — skip |
| 1979 | for arg in args.children: |
| 1980 | if arg.type == "template_string": |
| 1981 | # Skip dynamic template literals — path can't be statically resolved |
| 1982 | if any(c.type == "template_substitution" for c in arg.children): |
| 1983 | break |
| 1984 | raw = _read_text(arg, source).strip("`") |
| 1985 | elif arg.type == "string": |
| 1986 | raw = _read_text(arg, source).strip("'\" ") |
| 1987 | else: |
| 1988 | continue |
| 1989 | if not raw: |
| 1990 | break |
| 1991 | # Resolve path using the same logic as static imports. |
| 1992 | resolved = _resolve_js_import_target(raw, str_path) |
| 1993 | if resolved is None: |
| 1994 | break |
| 1995 | tgt_nid, _ = resolved |
| 1996 | pair = (caller_nid, tgt_nid) |
| 1997 | if pair not in seen_dyn_pairs: |
| 1998 | seen_dyn_pairs.add(pair) |
| 1999 | edges.append({ |
| 2000 | "source": caller_nid, |
| 2001 | "target": tgt_nid, |
| 2002 | # A deferred `import(...)` is a real dependency, so keep it as an |
| 2003 | # `imports_from` edge (visible in the graph) but mark it `deferred` |
| 2004 | # so find_import_cycles does not treat it as a static import and |
| 2005 | # report a phantom file cycle (#1241). |
| 2006 | "relation": "imports_from", |
| 2007 | "context": "import", |
| 2008 | "deferred": True, |
no test coverage detected