Coverage for circular_deps/parsers/typescript.py: 72%

1 from __future__ import annotations

2

3 import tree_sitter_typescript as tstypescript

4 from tree_sitter import Language

5 from tree_sitter import Parser

6

7 from circular_deps.models import ImportInfo

8 from circular_deps.parsers.base import ImportParser

9

10 TS_LANGUAGE = Language(tstypescript.language_typescript())

11 TSX_LANGUAGE = Language(tstypescript.language_tsx())

12

13 _IMPORT_NODE_TYPES = {"import_statement", "export_statement"}

14

15

16 def 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

21

22

23 class 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 = []

28

29 for node in root.children:

30 if node.type in _IMPORT_NODE_TYPES:

31 imports.extend(self._process_import_export_statement(node))

32

33 return self._filter_dynamic(imports, root)

34

35 def _process_import_export_statement(self, node):

36 imports = []

37

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 )

52

53 return imports

54

55 def _detect_import_type(self, module):

56 if module.startswith(".") or module.startswith(".."):

57 return "relative"

58 return "absolute"

59

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

70

71 return [imp for imp in imports if not imp.is_dynamic]

72

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