Map changed files to affected modules using module_tree.json. Uses substring matching (same as the CLI ``_invalidate_affected_modules``). Returns (affected_modules, cascade_parent_modules).
(
module_tree: Dict[str, Any],
changed_files: List[str],
)
| 233 | |
| 234 | |
| 235 | def _find_affected_modules( |
| 236 | module_tree: Dict[str, Any], |
| 237 | changed_files: List[str], |
| 238 | ) -> Tuple[set, set]: |
| 239 | """Map changed files to affected modules using module_tree.json. |
| 240 | |
| 241 | Uses substring matching (same as the CLI ``_invalidate_affected_modules``). |
| 242 | Returns (affected_modules, cascade_parent_modules). |
| 243 | """ |
| 244 | affected: set[str] = set() |
| 245 | cascade: set[str] = set() |
| 246 | |
| 247 | def _walk(tree: Dict, parents: list[str] | None = None): |
| 248 | if parents is None: |
| 249 | parents = [] |
| 250 | for mod_name, mod_info in tree.items(): |
| 251 | components = mod_info.get("components", []) |
| 252 | hit = False |
| 253 | for comp in components: |
| 254 | comp_file = comp.split("::")[0] |
| 255 | for cf in changed_files: |
| 256 | if comp_file == cf or comp_file.endswith("/" + cf) or cf.endswith("/" + comp_file): |
| 257 | hit = True |
| 258 | break |
| 259 | # Changed dir contains the component file, or vice versa |
| 260 | if cf.startswith(comp_file + "/") or comp_file.startswith(cf + "/"): |
| 261 | hit = True |
| 262 | break |
| 263 | if hit: |
| 264 | break |
| 265 | if hit: |
| 266 | affected.add(mod_name) |
| 267 | cascade.update(parents) |
| 268 | |
| 269 | children = mod_info.get("children", {}) |
| 270 | if isinstance(children, dict) and children: |
| 271 | _walk(children, parents + [mod_name]) |
| 272 | |
| 273 | _walk(module_tree) |
| 274 | |
| 275 | # overview.md depends on all child docs, always refresh if anything changed |
| 276 | if affected: |
| 277 | cascade.add("overview") |
| 278 | |
| 279 | return affected, cascade |
| 280 | |
| 281 | |
| 282 | def handle_analyze_repo( |
no test coverage detected