pub fn text(rule: &str) -> Option<&'static str> {
let t = match rule {
"unused-file" => {
"A module that nothing reachable from an entry point imports. \
Confidence: certain when there is no dynamic import sink in the project. \
Action: delete the file, or mark its module as an entry point."
}
"unused-import" => {
"An imported name that is never referenced outside its own import in \
the module. Confidence: certain in a regular module with no dynamic \
sink (auto-fixable); uncertain in `__init__.py` (likely a re-export). \
Action: remove the import."
}
"unused-variable" => {
"A local variable assigned but never read in its function (ruff F841). \
Confidence: likely. Not auto-fixed (the right-hand side may have side \
effects). Action: remove it, or prefix with `_`."
}
"unused-parameter" => {
"A function parameter never used in the body. Confidence: uncertain \
(it may satisfy an interface/override/callback signature). Action: \
remove it or prefix with `_`."
}
"unused-export" => {
"A top-level function/class never referenced outside its own \
module and not listed in `__all__`. Confidence: likely (dynamic access via \
getattr downgrades it). Action: remove it or make it private."
}
"unused-method" => {
"A class method never referenced anywhere as an attribute \
(`obj.m`/`self.m`/`Class.m`). Confidence: likely for private (`_m`), \
uncertain for public (may be an override/duck-typed/external API). \
Skips dunders, properties, static/class/abstract methods, and \
framework-registered methods. Action: remove it, or confirm the API use."
}
"unused-attribute" => {
"A class-level attribute/constant never referenced as an attribute \
and never read as a bare name. Confidence: likely for private, uncertain \
for public. Skips dataclass/Pydantic/NamedTuple/TypedDict fields. \
Action: remove it, or confirm dynamic use."
}
"unused-enum-member" => {
"An `enum.Enum` member never referenced. Confidence: uncertain — enums \
are often accessed dynamically (`Color[name]`, `Color(value)`, iteration, \
serialization). Action: remove it, or confirm dynamic/serialized use."
}
"unreachable-code" => {
"A statement that can never execute because it follows an \
unconditional terminator (`return`/`raise`/`break`/`continue`/`sys.exit()`) \
in the same block. Confidence: certain — provable syntactically. \
Action: remove the dead statement."
}
"private-type-leak" => {
"A public function/method whose signature references a private \
(`_Name`) type a caller cannot name (intentional `TypeVar`s are \
excluded). Confidence: likely. Action: make the type public, or stop \
exposing it in the public signature."
}
"unused-dependency" => {
"A distribution declared in pyproject/requirements but never \
imported. Confidence: likely. Action: remove it from your dependency list."
}
"transitive-dependency" => {
"A package imported and installed, but only because another dependency \
pulls it in (not declared directly). Confidence: likely. Action: add \
it to your direct dependencies so it survives the transitive dep changing."
}
"missing-dependency" => {
"A third-party module imported but absent from your declared \
dependencies (not stdlib, not first-party). Action: add it to your project metadata."
}
"misplaced-dev-dependency" => {
"A distribution declared only in a dev/test group (PEP 735 \
`dependency-groups`, Poetry/uv/pdm dev deps) but imported from \
production (non-test) code (deptry DEP004). Confidence: likely. \
Action: move it to your runtime dependencies."
}
"unresolved-import" => {
"An import that looks internal — relative (`from . import x`) or under \
a first-party top-level package — but resolves to no module in the \
project. Confidence: certain for relative imports, likely for absolute \
(path hacks exist). Action: fix the module path or remove the broken import."
}
"duplicate-export" => {
"An `__init__.py` re-exports the same name from two different \
modules; the later import silently shadows the earlier, so one \
re-export is dead and the public API is ambiguous. Confidence: likely. \
Action: keep a single source for the name."
}
"private-import" => {
"A module imports another *package*'s private (`_name`) symbol, \
reaching past its public API (tach/knip interface enforcement). \
Intra-package and relative imports are not flagged. Confidence: likely. \
Action: import via the package's public API, or make the name public."
}
"circular-dependency" => {
"A cycle of modules that import one another (Tarjan SCC). \
Confidence: certain — provable from static imports. Action: extract shared code \
to a lower module, or defer one import into function scope."
}
"layer-violation" => {
"A module imports a higher architectural layer than its own \
(per `architecture.layers`). Confidence: certain. Action: invert or relocate \
the dependency so lower layers never depend on higher ones."
}
"forbidden-import" => {
"An import that violates a declarative `contracts.forbidden` rule in \
`.mollifyrc` (module must not depend on another). Confidence: certain. \
Action: invert or relocate the dependency."
}
"independence-violation" => {
"Two modules declared independent (`contracts.independent`) import each \
other. Confidence: certain. Action: extract shared code to a common \
lower module."
}
"high-complexity" => {
"A function whose cyclomatic or cognitive complexity exceeds the \
configured threshold. Action: decompose it; extract helpers and flatten branches."
}
"duplication" => {
"A token sequence repeated across locations (exact clone found via a \
suffix array + LCP). Action: extract the shared logic into one definition."
}
"cold-code" => {
"A statically reachable function with zero executed lines in the \
supplied coverage report. Confidence: likely. Action: verify it is dead, then remove."
}
"commented-code" => {
"A comment whose text parses as Python code (dead code left in a \
comment). Confidence: likely. Action: delete it — version control \
remembers it."
}
"low-cohesion" => {
"A class whose methods share few instance attributes (high LCOM*) — \
it likely does several unrelated jobs. Confidence: uncertain. Action: \
split it into cohesive smaller classes."
}
"hotspot" => {
"A file that is both high-churn (git history) and high-complexity — the \
riskiest code to change. Action: prioritize it for refactoring and test coverage."
}
"untyped-function" | "untyped-public" => {
"A public function with no parameter or \
return type annotations. Action: add type hints to harden the public surface."
}
"respect-policy" | "policy-violation" => {
"A declarative `.mollifyrc` policy was \
violated (a forbidden import or call appeared). Confidence: certain. Action: remove \
or relocate the forbidden construct."
}
"dangerous-eval" => {
"A call to `eval`/`exec` on a non-literal argument. Action: replace \
with an explicit, safe parser or dispatch table."
}
"subprocess-shell-true" => {
"A subprocess call with `shell=True`. Action: pass an argv \
list instead of a shell string to avoid injection."
}
"unsafe-yaml-load" => "`yaml.load` without a safe loader. Action: use `yaml.safe_load`.",
"unsafe-deserialization" => {
"Deserializing untrusted data with pickle/marshal/shelve. \
Action: use a safe format such as JSON."
}
"tls-verify-disabled" => {
"TLS verification disabled (`verify=False`). Action: keep \
verification on; pin a CA bundle if needed."
}
"vulnerable-dependency" => {
"A pinned/locked dependency version falls in a known-vulnerable range \
from the local advisory DB (`.mollify/advisories.json`). Confidence: \
certain given the DB. Action: upgrade out of the affected range; refresh \
the DB with scripts/fetch-advisories.py."
}
"hardcoded-secret" => {
"A literal that looks like a credential assigned to a \
secret-named variable. Action: load it from the environment or a secret manager."
}
"weak-hash" => {
"Use of a broken hash (md5/sha1) (CWE-327). Action: use sha256+ \
or pass usedforsecurity=False if it's a non-security checksum."
}
"weak-cipher" => {
"A broken/weak cipher or ECB mode (CWE-327). Action: use an \
authenticated cipher such as AES-GCM or ChaCha20-Poly1305."
}
"insecure-random" => {
"`random` is not cryptographically secure (CWE-330). Action: use \
the `secrets` module for tokens/keys/nonces."
}
"sql-injection" => {
"SQL built from an f-string/concatenation/.format passed to an \
execute-style sink (CWE-89). Action: use parameterized queries."
}
"request-without-timeout" => {
"An HTTP request without a timeout can block indefinitely \
(CWE-400). Action: pass timeout=."
}
"flask-debug-true" => {
"A web app run with debug=True ships the interactive debugger — \
remote code execution in production (CWE-94). Action: drive debug \
from config/env and never enable it in production."
}
"jinja2-autoescape-false" => {
"A Jinja2 Environment created with autoescape=False risks XSS \
(CWE-79). Action: enable autoescaping (or use select_autoescape)."
}
"try-except-pass" => {
"A broad `except: pass` (bare or Exception/BaseException) silently \
swallows all errors (CWE-703). Confidence: uncertain. Action: log or \
handle the error, or narrow the exception type."
}
_ => return None,
};
Some(t)
}
pub const RULES: &[&str] = &[
"unused-file",
"unused-export",
"unused-import",
"unused-variable",
"unused-parameter",
"unused-method",
"unused-attribute",
"unused-enum-member",
"unreachable-code",
"unused-dependency",
"missing-dependency",
"transitive-dependency",
"misplaced-dev-dependency",
"unresolved-import",
"duplicate-export",
"private-import",
"circular-dependency",
"layer-violation",
"forbidden-import",
"independence-violation",
"high-complexity",
"duplication",
"cold-code",
"commented-code",
"hotspot",
"low-cohesion",
"untyped-function",
"private-type-leak",
"policy-violation",
"dangerous-eval",
"subprocess-shell-true",
"unsafe-yaml-load",
"unsafe-deserialization",
"tls-verify-disabled",
"hardcoded-secret",
"weak-hash",
"weak-cipher",
"insecure-random",
"sql-injection",
"request-without-timeout",
"flask-debug-true",
"jinja2-autoescape-false",
"try-except-pass",
"vulnerable-dependency",
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_rules_explain_and_unknown_is_none() {
assert!(text("circular-dependency").unwrap().contains("cycle"));
assert!(text("layer-violation").is_some());
assert!(text("not-a-rule").is_none());
for r in RULES {
assert!(text(r).is_some(), "no explanation for {r}");
}
}
}