# file: scripts/_rust_audit_common.py # version: 1 """Small shared lexical and file-selection helpers for read-only Rust audits.""" from __future__ import annotations import pathlib _IGNORED = frozenset({".git", ".venv", "__pycache__", "target", "builds"}) def selected_files(raw_paths: list[str]) -> tuple[list[pathlib.Path], list[str]]: """Select explicit Rust files and directories, without following generated trees.""" files: set[pathlib.Path] = set() errors: list[str] = [] for raw in raw_paths: path = pathlib.Path(raw) if path.is_dir(): files.update( candidate for candidate in path.rglob("*.rs") if candidate.is_file() and not (set(candidate.parts) & _IGNORED) ) elif path.is_file() and path.suffix == ".rs": files.add(path) else: errors.append(f"unsupported or missing Rust path: {path}") return sorted(files), errors def mask_source(text: str) -> tuple[str, set[int]]: """Mask comments and literals, preserving offsets and structural punctuation. Return line numbers whose first character is inside a multiline literal, so an empty line in a raw string cannot be mistaken for an empty code line. This is a lexical helper, not a full Rust parser or a compiler substitute. """ output = list(text) protected: set[int] = set() index = 0 line = 1 state = "code" block_depth = 0 raw_hashes = 0 while index < len(text): char = text[index] nxt = text[index + 1] if index + 1 < len(text) else "" if char == "\n": line += 1 if state in {"string", "raw_string"}: protected.add(line) if state == "line_comment": state = "code" index += 1 continue if state == "code": if char == "/" and nxt in {"/", "*"}: output[index:index + 2] = [" ", " "] state = "line_comment" if nxt == "/" else "block_comment" block_depth = 1 if nxt == "*" else 0 index += 2 continue if char == '"': output[index] = " " state = "string" index += 1 continue if char == "'": # Lifetimes have no nearby closing quote; char literals do. probe = index + 1 escaped = False found = False while probe < min(len(text), index + 8) and text[probe] != "\n": if text[probe] == "'" and not escaped: found = True break escaped = text[probe] == "\\" and not escaped probe += 1 if found: output[index] = " " state = "char" index += 1 continue # Also covers r#"..."#, br#"..."# and cr#"..."#. if char == "r" and (index == 0 or text[index - 1] not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"): probe = index + 1 while probe < len(text) and text[probe] == "#": probe += 1 if probe < len(text) and text[probe] == '"': output[index:probe + 1] = [" "] * (probe + 1 - index) raw_hashes = probe - index - 1 state = "raw_string" index = probe + 1 continue index += 1 continue if state == "line_comment": output[index] = " " elif state == "block_comment": output[index] = " " if char == "/" and nxt == "*": output[index + 1] = " " block_depth += 1 index += 2 continue if char == "*" and nxt == "/": output[index + 1] = " " block_depth -= 1 if block_depth == 0: state = "code" index += 2 continue elif state in {"string", "char"}: output[index] = " " if char == "\\" and index + 1 < len(text): if text[index + 1] != "\n": output[index + 1] = " " index += 2 continue if char == ('"' if state == "string" else "'"): state = "code" elif state == "raw_string": output[index] = " " if char == '"' and text.startswith("#" * raw_hashes, index + 1): output[index + 1:index + 1 + raw_hashes] = [" "] * raw_hashes index += 1 + raw_hashes state = "code" continue index += 1 return "".join(output), protected def line_depths(masked: str) -> list[int]: """Return brace depth at the start of each line, excluding literals.""" depths: list[int] = [] depth = 0 for line in masked.splitlines(): depths.append(depth) depth = max(0, depth + line.count("{") - line.count("}")) return depths