Option C Tools
Loading...
Searching...
No Matches
lint_cache.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oct/linter/lint_cache.py
4
5"""
6Purpose
7-------
8FS-510 — incremental lint cache. Content-hash keyed so unchanged files
9are not re-linted on subsequent runs.
10
11Responsibilities
12----------------
13- Compute deterministic content hashes for source files.
14- Store and retrieve per-file lint results from a JSON cache file.
15- Invalidate entries when file content, active rules, or OCT version change.
16- Thread-safe read/write via ``threading.Lock``.
17
18Diagnostics
19-----------
20Domain: OCT-LINTER
21Levels:
22 L3 — cache hits/misses
23 L4 — cache I/O details
24
25Contracts
26---------
27- Cache file lives at ``.option_c/cache/lint-cache.json``.
28- Corrupt or missing cache file is silently replaced (no crash).
29- Cache is never authoritative — a ``--no-cache`` flag bypasses reads.
30"""
31
32from __future__ import annotations
33
34import hashlib
35import json
36import threading
37from dataclasses import dataclass, field, asdict
38from pathlib import Path
39from typing import Any
40
41
42_SCHEMA_VERSION = "1.0"
43
44
45def content_hash(text: str) -> str:
46 """Return a SHA-256 hex digest of *text*."""
47 return hashlib.sha256(text.encode("utf-8")).hexdigest()
48
49
50def rules_hash(active_rules: dict[str, bool]) -> str:
51 """Return a SHA-256 hex digest of the sorted active-rules dict."""
52 canonical = json.dumps(active_rules, sort_keys=True)
53 return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
54
55
56@dataclass
58 """One cached lint result for a single file."""
59
60 content_hash: str
61 rule_results: dict[str, bool]
62 json_detail: dict[str, Any]
63 oct_version: str
64 timestamp: str
65
66
68 """Thread-safe, content-addressed lint result cache.
69
70 Loads from *cache_path* on init; call :meth:`save` to persist.
71 """
72
73 def __init__(self, cache_path: Path, oct_version: str) -> None:
74 self._path = cache_path
75 self._oct_version = oct_version
76 self._lock = threading.Lock()
77 self._entries: dict[str, dict] = {}
78 self._load()
79
80 def _load(self) -> None:
81 if not self._path.is_file():
82 return
83 try:
84 raw = json.loads(self._path.read_text(encoding="utf-8"))
85 if not isinstance(raw, dict):
86 return
87 if raw.get("_schema_version") != _SCHEMA_VERSION:
88 return
89 if raw.get("oct_version") != self._oct_version:
90 return
91 entries = raw.get("entries")
92 if isinstance(entries, dict):
93 self._entries = entries
94 except (json.JSONDecodeError, OSError):
95 self._entries = {}
96
97 def get(self, file_hash: str, active_rules_hash: str) -> LintCacheEntry | None:
98 """Return cached entry if content + rules match, else ``None``."""
99 with self._lock:
100 key = f"{file_hash}:{active_rules_hash}"
101 entry = self._entries.get(key)
102 if entry is None:
103 return None
104 if entry.get("content_hash") != file_hash:
105 return None
106 return LintCacheEntry(
107 content_hash=entry["content_hash"],
108 rule_results=entry["rule_results"],
109 json_detail=entry["json_detail"],
110 oct_version=entry["oct_version"],
111 timestamp=entry["timestamp"],
112 )
113
114 def put(
115 self,
116 file_hash: str,
117 active_rules_hash: str,
118 entry: LintCacheEntry,
119 ) -> None:
120 """Store a lint result in the cache."""
121 with self._lock:
122 key = f"{file_hash}:{active_rules_hash}"
123 self._entries[key] = asdict(entry)
124
125 def save(self) -> None:
126 """Write the cache to disk. Creates parent directories if needed."""
127 with self._lock:
128 self._path.parent.mkdir(parents=True, exist_ok=True)
129 payload = {
130 "_schema_version": _SCHEMA_VERSION,
131 "oct_version": self._oct_version,
132 "entries": self._entries,
133 }
134 self._path.write_text(
135 json.dumps(payload, indent=2), encoding="utf-8",
136 )
137
138 @property
139 def entry_count(self) -> int:
140 """Number of cached entries (for diagnostics)."""
141 with self._lock:
142 return len(self._entries)
Definition lint_cache.py:57
LintCacheEntry|None get(self, str file_hash, str active_rules_hash)
Definition lint_cache.py:97
None put(self, str file_hash, str active_rules_hash, LintCacheEntry entry)
None __init__(self, Path cache_path, str oct_version)
Definition lint_cache.py:73
str content_hash(str text)
Definition lint_cache.py:45
str rules_hash(dict[str, bool] active_rules)
Definition lint_cache.py:50