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
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
1from __future__ import annotations
3import json
4import os
5from pathlib import Path
8VALID_MODULE_RESOLUTIONS = {"node", "node16", "nodenext", "bundler"}
11class TsconfigParser:
12 """Parse TypeScript tsconfig.json files to extract module resolution settings."""
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.
18 Args:
19 filepath: Path to a file (directory will be checked then walked up)
21 Returns:
22 Absolute path to closest tsconfig.json, or None if not found
23 """
24 filepath = Path(filepath).resolve()
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
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())
39 parent = current_dir.parent
40 if parent == current_dir:
41 # Reached root
42 break
43 current_dir = parent
45 return None
47 @staticmethod
48 def parse_tsconfig(filepath: str | Path) -> dict | None:
49 """Parse tsconfig.json and return dict.
51 Silent error handling: returns None for invalid JSON or parse errors.
53 Args:
54 filepath: Path to tsconfig.json file
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
68 @staticmethod
69 def get_module_resolution(config: dict) -> str | None:
70 """Extract compilerOptions.moduleResolution from tsconfig config.
72 Returns only valid values. Returns None for invalid or missing values.
74 Args:
75 config: Parsed tsconfig dict
77 Returns:
78 One of: "node", "node16", "nodenext", "bundler", or None
79 """
80 if not isinstance(config, dict):
81 return None
83 compiler_options = config.get("compilerOptions")
84 if not isinstance(compiler_options, dict):
85 return None
87 module_resolution = compiler_options.get("moduleResolution")
88 if not isinstance(module_resolution, str):
89 return None
91 # Return only valid values
92 if module_resolution in VALID_MODULE_RESOLUTIONS:
93 return module_resolution
95 return None