Per-file IWYU result cache with source-tree-aware invalidation.
| 83 | |
| 84 | |
| 85 | class IWYUCache: |
| 86 | """Per-file IWYU result cache with source-tree-aware invalidation.""" |
| 87 | |
| 88 | def __init__(self, enabled: bool = True) -> None: |
| 89 | self._enabled = enabled |
| 90 | self._data: dict[str, dict[str, list[str] | float | str]] = {} |
| 91 | self._hits = 0 |
| 92 | self._misses = 0 |
| 93 | self._source_tree_hash = "" |
| 94 | if enabled: |
| 95 | self._load() |
| 96 | |
| 97 | def set_source_tree_hash(self, h: str) -> None: |
| 98 | """Set the source tree hash for this session. |
| 99 | |
| 100 | Must be called before get()/put(). Computed once by the caller |
| 101 | from the full list of header files. |
| 102 | """ |
| 103 | self._source_tree_hash = h |
| 104 | |
| 105 | def _load(self) -> None: |
| 106 | """Load cache from disk.""" |
| 107 | try: |
| 108 | if _CACHE_FILE.exists(): |
| 109 | with open(_CACHE_FILE, "r") as f: |
| 110 | raw = json.load(f) |
| 111 | if isinstance(raw, dict): |
| 112 | self._data = raw |
| 113 | except (json.JSONDecodeError, OSError): |
| 114 | self._data = {} |
| 115 | |
| 116 | def _save(self) -> None: |
| 117 | """Persist cache to disk.""" |
| 118 | if not self._enabled: |
| 119 | return |
| 120 | try: |
| 121 | _CACHE_DIR.mkdir(parents=True, exist_ok=True) |
| 122 | with open(_CACHE_FILE, "w") as f: |
| 123 | json.dump(self._data, f, separators=(",", ":")) |
| 124 | except OSError: |
| 125 | pass # Non-fatal |
| 126 | |
| 127 | def get(self, file_path: Path, compiler_args_key: str) -> list[str] | None: |
| 128 | """Look up cached IWYU result for a file. |
| 129 | |
| 130 | Args: |
| 131 | file_path: Source file path. |
| 132 | compiler_args_key: Compiler args fingerprint. |
| 133 | |
| 134 | Returns: |
| 135 | List of removal strings if cached (empty list = clean), |
| 136 | or None on cache miss. |
| 137 | """ |
| 138 | if not self._enabled: |
| 139 | self._misses += 1 |
| 140 | return None |
| 141 | |
| 142 | file_hash = _compute_file_hash( |