Coverage for circular_deps / parsers / typescript.py: 72%
54 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
1from __future__ import annotations
3import tree_sitter_typescript as tstypescript
4from tree_sitter import Language
5from tree_sitter import Parser
7from circular_deps.models import ImportInfo
8from circular_deps.parsers.base import ImportParser
10TS_LANGUAGE = Language(tstypescript.language_typescript())
11TSX_LANGUAGE = Language(tstypescript.language_tsx())
13_IMPORT_NODE_TYPES = {"import_statement", "export_statement"}
16def parse(code, tsx=False):
17 lang = TSX_LANGUAGE if tsx else TS_LANGUAGE
18 parser_obj = Parser(lang)
19 tree = parser_obj.parse(code.encode())
20 return tree.root_node
23class TypeScriptImportParser(ImportParser):
24 def extract_imports(self, source, filepath):
25 tsx = filepath.endswith(".tsx") or filepath.endswith(".jsx")
26 root = parse(source, tsx=tsx)
27 imports = []
29 for node in root.children:
30 if node.type in _IMPORT_NODE_TYPES:
31 imports.extend(self._process_import_export_statement(node))
33 return self._filter_dynamic(imports, root)
35 def _process_import_export_statement(self, node):
36 imports = []
38 for child in node.children:
39 if child.type == "string":
40 module = child.text.decode().strip("\"'")
41 if module:
42 imports.append(
43 ImportInfo(
44 raw_module=module,
45 resolved_path=None,
46 import_type=self._detect_import_type(module),
47 line=node.start_point[0] + 1,
48 is_dynamic=False,
49 node_type=node.type,
50 )
51 )
53 return imports
55 def _detect_import_type(self, module):
56 if module.startswith(".") or module.startswith(".."):
57 return "relative"
58 return "absolute"
60 def _filter_dynamic(self, imports, root):
61 for node in root.children:
62 if node.type == "expression_statement":
63 for expr in node.children:
64 if expr.type == "call":
65 if self._is_dynamic_import_call(expr):
66 line = node.start_point[0] + 1
67 for imp in imports:
68 if imp.line == line:
69 imp.is_dynamic = True
71 return [imp for imp in imports if not imp.is_dynamic]
73 def _is_dynamic_import_call(self, call_node):
74 for child in call_node.children:
75 if child.type == "import":
76 return True
77 for gc in child.children:
78 if gc.type == "import":
79 return True
80 return False