Coverage for circular_deps / resolvers / tsconfig.py: 89%

44 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-08 15:04 -0800

1from __future__ import annotations 

2 

3import json 

4import os 

5from pathlib import Path 

6 

7 

8VALID_MODULE_RESOLUTIONS = {"node", "node16", "nodenext", "bundler"} 

9 

10 

11class TsconfigParser: 

12 """Parse TypeScript tsconfig.json files to extract module resolution settings.""" 

13 

14 @staticmethod 

15 def find_tsconfig_for_file(filepath: str | Path) -> str | None: 

16 """Find tsconfig.json closest to given file by walking up directory tree. 

17 

18 Args: 

19 filepath: Path to a file (directory will be checked then walked up) 

20 

21 Returns: 

22 Absolute path to closest tsconfig.json, or None if not found 

23 """ 

24 filepath = Path(filepath).resolve() 

25 

26 # If filepath is a file, start from its directory 

27 if filepath.is_file(): 

28 search_dir = filepath.parent 

29 else: 

30 search_dir = filepath 

31 

32 # Walk up the directory tree looking for tsconfig.json 

33 current_dir = search_dir 

34 while True: 

35 tsconfig_path = current_dir / "tsconfig.json" 

36 if tsconfig_path.exists() and tsconfig_path.is_file(): 

37 return str(tsconfig_path.absolute()) 

38 

39 parent = current_dir.parent 

40 if parent == current_dir: 

41 # Reached root 

42 break 

43 current_dir = parent 

44 

45 return None 

46 

47 @staticmethod 

48 def parse_tsconfig(filepath: str | Path) -> dict | None: 

49 """Parse tsconfig.json and return dict. 

50 

51 Silent error handling: returns None for invalid JSON or parse errors. 

52 

53 Args: 

54 filepath: Path to tsconfig.json file 

55 

56 Returns: 

57 Parsed config dict, or None if file cannot be parsed 

58 """ 

59 try: 

60 with open(filepath, "r", encoding="utf-8") as f: 

61 content = f.read() 

62 # Basic JSON parsing (no comments support - standard JSON only) 

63 config = json.loads(content) 

64 return config 

65 except (json.JSONDecodeError, IOError, OSError): 

66 return None 

67 

68 @staticmethod 

69 def get_module_resolution(config: dict) -> str | None: 

70 """Extract compilerOptions.moduleResolution from tsconfig config. 

71 

72 Returns only valid values. Returns None for invalid or missing values. 

73 

74 Args: 

75 config: Parsed tsconfig dict 

76 

77 Returns: 

78 One of: "node", "node16", "nodenext", "bundler", or None 

79 """ 

80 if not isinstance(config, dict): 

81 return None 

82 

83 compiler_options = config.get("compilerOptions") 

84 if not isinstance(compiler_options, dict): 

85 return None 

86 

87 module_resolution = compiler_options.get("moduleResolution") 

88 if not isinstance(module_resolution, str): 

89 return None 

90 

91 # Return only valid values 

92 if module_resolution in VALID_MODULE_RESOLUTIONS: 

93 return module_resolution 

94 

95 return None