Find functions/classes with no callers, no test refs, no importers, and no references. Entry points (functions matching framework decorators or conventional name patterns like ``main``, ``test_*``, ``handle_*``) are excluded. .. note:: **Caveats — dynamic dispatch patterns.**
(
store: GraphStore,
kind: Optional[str] = None,
file_pattern: Optional[str] = None,
root: Optional[Union[str, Path]] = None,
)
| 238 | |
| 239 | |
| 240 | def find_dead_code( |
| 241 | store: GraphStore, |
| 242 | kind: Optional[str] = None, |
| 243 | file_pattern: Optional[str] = None, |
| 244 | root: Optional[Union[str, Path]] = None, |
| 245 | ) -> list[dict[str, Any]]: |
| 246 | """Find functions/classes with no callers, no test refs, no importers, and no references. |
| 247 | |
| 248 | Entry points (functions matching framework decorators or conventional name |
| 249 | patterns like ``main``, ``test_*``, ``handle_*``) are excluded. |
| 250 | |
| 251 | .. note:: |
| 252 | |
| 253 | **Caveats — dynamic dispatch patterns.** Static analysis cannot track |
| 254 | all runtime-determined call patterns. Functions registered via fully |
| 255 | dynamic keys (``map[computedKey()] = fn``), ``Reflect.apply``, or |
| 256 | runtime ``require()`` may still appear as dead code. Treat results as |
| 257 | hints, especially for TypeScript projects that use map-based dispatch, |
| 258 | plugin registries, or dynamic requires. |
| 259 | |
| 260 | Args: |
| 261 | store: The GraphStore instance. |
| 262 | kind: Optional filter (e.g. ``"Function"`` or ``"Class"``). |
| 263 | file_pattern: Optional file-path substring filter. |
| 264 | root: Optional repo root path for computing ``relative_path``. |
| 265 | |
| 266 | Returns: |
| 267 | List of dead-code dicts with name, qualified_name, kind, file_path, |
| 268 | relative_path, line, and language fields. |
| 269 | """ |
| 270 | # Query candidate nodes. |
| 271 | candidates = store.get_nodes_by_kind( |
| 272 | kinds=[kind] if kind else ["Function", "Class"], |
| 273 | file_pattern=file_pattern, |
| 274 | ) |
| 275 | |
| 276 | # Build set of class names referenced in function type annotations. |
| 277 | type_ref_names = _collect_type_referenced_names(store) |
| 278 | |
| 279 | # Build class hierarchy: class_qualified_name -> [bare_base_names] |
| 280 | class_bases: dict[str, list[str]] = {} |
| 281 | conn = store._conn |
| 282 | for row in conn.execute( |
| 283 | "SELECT source_qualified, target_qualified FROM edges WHERE kind = 'INHERITS'" |
| 284 | ).fetchall(): |
| 285 | base = row[1].rsplit("::", 1)[-1] if "::" in row[1] else row[1] |
| 286 | class_bases.setdefault(row[0], []).append(base) |
| 287 | |
| 288 | # Build import graph: file_path -> set of file_paths it imports from. |
| 289 | # Used to filter bare-name caller matches to plausible callers. |
| 290 | importer_files: dict[str, set[str]] = {} |
| 291 | for row in conn.execute( |
| 292 | "SELECT file_path, target_qualified FROM edges WHERE kind = 'IMPORTS_FROM'" |
| 293 | ).fetchall(): |
| 294 | importer_files.setdefault(row[0], set()).add(row[1]) |
| 295 | |
| 296 | # Build set of globally unique names (only one non-test node with that name). |
| 297 | # For unique names, any bare-name CALLS edge is reliable — no ambiguity. |