Skip to main content

rust_doctor/policy/
catalog.rs

1use serde::Serialize;
2
3use super::RuleLevel;
4
5#[cfg(test)]
6mod tests;
7#[cfg(test)]
8mod validate;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
11#[serde(rename_all = "kebab-case")]
12pub enum Producer {
13    Clippy,
14    CargoHealth,
15    SourceKernel,
16    Structure,
17    Repo,
18}
19
20/// One catalogued rule, as the outside world reads it.
21///
22/// `RuleDefinition` stays crate-private because it is the shape the scan
23/// compiles against; this is the published projection of it, and the reason the
24/// website can state what the tool checks without anyone retyping the list.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
26pub struct CatalogEntry {
27    pub id: &'static str,
28    pub category: &'static str,
29    pub producer: Producer,
30    pub default_level: RuleLevel,
31    pub tier: RuleTier,
32    pub help: &'static str,
33}
34
35/// Every catalogued rule, in catalog order.
36#[must_use]
37pub fn catalog() -> Vec<CatalogEntry> {
38    CATALOG
39        .iter()
40        .map(|definition| CatalogEntry {
41            id: definition.id,
42            category: definition.category,
43            producer: definition.producer,
44            default_level: definition.default_level,
45            tier: definition.tier,
46            help: definition.help,
47        })
48        .collect()
49}
50
51/// Criticality of a rule, independent of `default_level` and of the effective
52/// severity of a diagnostic.
53///
54/// The tier only drives the `core-v3` score: it imposes a cap on the dimension
55/// concerned and on the overall score. It enters neither `base_severity` nor
56/// `fingerprint()`, so it moves no baseline.
57///
58/// The declared order runs from gravest to least grave: `P0 < P1 < P2 < P3`, so
59/// the worst tier of a set is its minimum.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
61pub enum RuleTier {
62    P0,
63    P1,
64    P2,
65    P3,
66}
67
68impl RuleTier {
69    #[cfg(test)]
70    pub(crate) const ALL: [Self; 4] = [Self::P0, Self::P1, Self::P2, Self::P3];
71
72    pub const fn as_str(self) -> &'static str {
73        match self {
74            Self::P0 => "P0",
75            Self::P1 => "P1",
76            Self::P2 => "P2",
77            Self::P3 => "P3",
78        }
79    }
80
81    /// Closed reading of a published tier. Any other value is refused without
82    /// echoing the input. It is what every frozen record compares against, so
83    /// it lives with the type rather than with any one of its readers.
84    #[cfg(test)]
85    pub(crate) fn parse(value: &str) -> Option<Self> {
86        match value {
87            "P0" => Some(Self::P0),
88            "P1" => Some(Self::P1),
89            "P2" => Some(Self::P2),
90            "P3" => Some(Self::P3),
91            _ => None,
92        }
93    }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
97pub(crate) struct RuleDefinition {
98    pub(crate) id: &'static str,
99    pub(crate) category: &'static str,
100    pub(crate) producer: Producer,
101    pub(crate) default_level: RuleLevel,
102    pub(crate) tier: RuleTier,
103    pub(crate) help: &'static str,
104}
105
106/// Admissible categories, sorted: `find`/`validate_catalog` look them up by
107/// binary search. Each maps to a score dimension through
108/// `audit::category_mapping`, so opening a category makes its dimension
109/// reachable.
110pub(crate) const CATEGORIES: [&str; 6] = [
111    "correctness",
112    "dependencies",
113    "maintainability",
114    "performance",
115    "reliability",
116    "security",
117];
118
119pub(crate) static CLIPPY_ARC_WITH_NON_SEND_SYNC: RuleDefinition = RuleDefinition {
120    id: "clippy::arc_with_non_send_sync",
121    category: "correctness",
122    producer: Producer::Clippy,
123    default_level: RuleLevel::Warn,
124    tier: RuleTier::P1,
125    help: "Use Rc for single-threaded sharing, or make the inner value Send and Sync before sharing it across threads.",
126};
127pub(crate) static CLIPPY_AWAIT_HOLDING_LOCK: RuleDefinition = RuleDefinition {
128    id: "clippy::await_holding_lock",
129    category: "correctness",
130    producer: Producer::Clippy,
131    default_level: RuleLevel::Warn,
132    tier: RuleTier::P1,
133    help: "Drop the guard before the await point, or use a lock designed to be held across await.",
134};
135pub(crate) static CLIPPY_AWAIT_HOLDING_REFCELL_REF: RuleDefinition = RuleDefinition {
136    id: "clippy::await_holding_refcell_ref",
137    category: "correctness",
138    producer: Producer::Clippy,
139    default_level: RuleLevel::Warn,
140    tier: RuleTier::P1,
141    help: "Copy the borrowed value and drop the borrow before the await point.",
142};
143pub(crate) static CLIPPY_DBG_MACRO: RuleDefinition = RuleDefinition {
144    id: "clippy::dbg_macro",
145    category: "maintainability",
146    producer: Producer::Clippy,
147    default_level: RuleLevel::Warn,
148    tier: RuleTier::P3,
149    help: "Remove dbg! or replace it with intentional logging.",
150};
151pub(crate) static CLIPPY_EXIT: RuleDefinition = RuleDefinition {
152    id: "clippy::exit",
153    category: "reliability",
154    producer: Producer::Clippy,
155    default_level: RuleLevel::Warn,
156    tier: RuleTier::P2,
157    help: "Return an error to the caller and let the entry point decide the exit status.",
158};
159pub(crate) static CLIPPY_EXPECT_USED: RuleDefinition = RuleDefinition {
160    id: "clippy::expect_used",
161    category: "reliability",
162    producer: Producer::Clippy,
163    default_level: RuleLevel::Warn,
164    tier: RuleTier::P3,
165    help: "Propagate the error with ? or handle the missing value explicitly instead of panicking.",
166};
167pub(crate) static CLIPPY_FORMAT_COLLECT: RuleDefinition = RuleDefinition {
168    id: "clippy::format_collect",
169    category: "performance",
170    producer: Producer::Clippy,
171    default_level: RuleLevel::Warn,
172    tier: RuleTier::P3,
173    help: "Write into one String with write! or push_str instead of allocating one String per item.",
174};
175pub(crate) static CLIPPY_INDEXING_SLICING: RuleDefinition = RuleDefinition {
176    id: "clippy::indexing_slicing",
177    category: "reliability",
178    producer: Producer::Clippy,
179    default_level: RuleLevel::Warn,
180    tier: RuleTier::P3,
181    help: "Use get or get_mut and handle the absent element instead of indexing, which panics out of bounds.",
182};
183pub(crate) static CLIPPY_LARGE_TYPES_PASSED_BY_VALUE: RuleDefinition = RuleDefinition {
184    id: "clippy::large_types_passed_by_value",
185    category: "performance",
186    producer: Producer::Clippy,
187    default_level: RuleLevel::Warn,
188    tier: RuleTier::P3,
189    help: "Pass the large value by reference to avoid copying it at every call.",
190};
191pub(crate) static CLIPPY_MANUAL_MEMCPY: RuleDefinition = RuleDefinition {
192    id: "clippy::manual_memcpy",
193    category: "performance",
194    producer: Producer::Clippy,
195    default_level: RuleLevel::Warn,
196    tier: RuleTier::P3,
197    help: "Use copy_from_slice or clone_from_slice instead of copying element by element.",
198};
199pub(crate) static CLIPPY_MEM_FORGET: RuleDefinition = RuleDefinition {
200    id: "clippy::mem_forget",
201    category: "reliability",
202    producer: Producer::Clippy,
203    default_level: RuleLevel::Warn,
204    tier: RuleTier::P2,
205    help: "Avoid leaking a value with drop semantics; use an explicit ownership or lifetime strategy.",
206};
207pub(crate) static CLIPPY_MISSING_SAFETY_DOC: RuleDefinition = RuleDefinition {
208    id: "clippy::missing_safety_doc",
209    category: "maintainability",
210    producer: Producer::Clippy,
211    default_level: RuleLevel::Warn,
212    tier: RuleTier::P3,
213    help: "Document in a `# Safety` section the invariants the caller must uphold before calling.",
214};
215pub(crate) static CLIPPY_MUT_MUTEX_LOCK: RuleDefinition = RuleDefinition {
216    id: "clippy::mut_mutex_lock",
217    category: "correctness",
218    producer: Producer::Clippy,
219    default_level: RuleLevel::Warn,
220    tier: RuleTier::P2,
221    help: "Use get_mut when the mutex is already exclusively borrowed; locking it again can deadlock.",
222};
223pub(crate) static CLIPPY_NON_SEND_FIELDS_IN_SEND_TY: RuleDefinition = RuleDefinition {
224    id: "clippy::non_send_fields_in_send_ty",
225    category: "correctness",
226    producer: Producer::Clippy,
227    default_level: RuleLevel::Warn,
228    tier: RuleTier::P1,
229    help: "Remove the unsafe Send implementation or ensure every field is safe to send between threads.",
230};
231pub(crate) static CLIPPY_PANIC: RuleDefinition = RuleDefinition {
232    id: "clippy::panic",
233    category: "reliability",
234    producer: Producer::Clippy,
235    default_level: RuleLevel::Warn,
236    tier: RuleTier::P3,
237    help: "Return an error instead of aborting the process on an input the caller can recover from.",
238};
239pub(crate) static CLIPPY_PANIC_IN_RESULT_FN: RuleDefinition = RuleDefinition {
240    id: "clippy::panic_in_result_fn",
241    category: "correctness",
242    producer: Producer::Clippy,
243    default_level: RuleLevel::Warn,
244    tier: RuleTier::P2,
245    help: "A function that already returns Result should report the failure as Err instead of panicking.",
246};
247pub(crate) static CLIPPY_PERMISSIONS_SET_READONLY_FALSE: RuleDefinition = RuleDefinition {
248    id: "clippy::permissions_set_readonly_false",
249    category: "security",
250    producer: Producer::Clippy,
251    default_level: RuleLevel::Warn,
252    tier: RuleTier::P1,
253    help: "Set explicit Unix permission bits instead of clearing readonly on Unix.",
254};
255pub(crate) static CLIPPY_PRINT_STDERR: RuleDefinition = RuleDefinition {
256    id: "clippy::print_stderr",
257    category: "maintainability",
258    producer: Producer::Clippy,
259    default_level: RuleLevel::Warn,
260    tier: RuleTier::P3,
261    help: "Write to a caller-provided writer or a logger instead of hard-wiring stderr.",
262};
263pub(crate) static CLIPPY_PRINT_STDOUT: RuleDefinition = RuleDefinition {
264    id: "clippy::print_stdout",
265    category: "maintainability",
266    producer: Producer::Clippy,
267    default_level: RuleLevel::Warn,
268    tier: RuleTier::P3,
269    help: "Write to a caller-provided writer or a logger instead of hard-wiring stdout.",
270};
271pub(crate) static CLIPPY_PTR_ARG: RuleDefinition = RuleDefinition {
272    id: "clippy::ptr_arg",
273    category: "performance",
274    producer: Producer::Clippy,
275    default_level: RuleLevel::Warn,
276    tier: RuleTier::P3,
277    help: "Take &[T] or &str so callers can pass any borrowed slice without owning one first.",
278};
279pub(crate) static CLIPPY_RC_BUFFER: RuleDefinition = RuleDefinition {
280    id: "clippy::rc_buffer",
281    category: "performance",
282    producer: Producer::Clippy,
283    default_level: RuleLevel::Warn,
284    tier: RuleTier::P3,
285    help: "Share the slice itself with Rc<str> or Rc<[T]> instead of wrapping an owned buffer.",
286};
287pub(crate) static CLIPPY_RC_MUTEX: RuleDefinition = RuleDefinition {
288    id: "clippy::rc_mutex",
289    category: "correctness",
290    producer: Producer::Clippy,
291    default_level: RuleLevel::Warn,
292    tier: RuleTier::P2,
293    help: "Use RefCell inside Rc for single-threaded sharing, or Arc<Mutex<T>> when the value really crosses threads.",
294};
295pub(crate) static CLIPPY_REDUNDANT_ALLOCATION: RuleDefinition = RuleDefinition {
296    id: "clippy::redundant_allocation",
297    category: "performance",
298    producer: Producer::Clippy,
299    default_level: RuleLevel::Warn,
300    tier: RuleTier::P2,
301    help: "Remove the inner allocation; one pointer indirection is enough.",
302};
303pub(crate) static CLIPPY_STABLE_SORT_PRIMITIVE: RuleDefinition = RuleDefinition {
304    id: "clippy::stable_sort_primitive",
305    category: "performance",
306    producer: Producer::Clippy,
307    default_level: RuleLevel::Warn,
308    tier: RuleTier::P3,
309    help: "Use sort_unstable on primitives; stability carries no meaning and costs an allocation.",
310};
311pub(crate) static CLIPPY_STRING_SLICE: RuleDefinition = RuleDefinition {
312    id: "clippy::string_slice",
313    category: "reliability",
314    producer: Producer::Clippy,
315    default_level: RuleLevel::Warn,
316    tier: RuleTier::P3,
317    help: "Use get on the string range and handle the absent slice; byte indexing panics inside a UTF-8 character.",
318};
319pub(crate) static CLIPPY_SUSPICIOUS_COMMAND_ARG_SPACE: RuleDefinition = RuleDefinition {
320    id: "clippy::suspicious_command_arg_space",
321    category: "correctness",
322    producer: Producer::Clippy,
323    default_level: RuleLevel::Warn,
324    tier: RuleTier::P2,
325    help: "Pass each process argument separately instead of embedding spaces in one argument.",
326};
327pub(crate) static CLIPPY_TODO: RuleDefinition = RuleDefinition {
328    id: "clippy::todo",
329    category: "correctness",
330    producer: Producer::Clippy,
331    default_level: RuleLevel::Warn,
332    tier: RuleTier::P2,
333    help: "Replace todo! with the intended implementation or remove the reachable placeholder.",
334};
335pub(crate) static CLIPPY_TOO_MANY_ARGUMENTS: RuleDefinition = RuleDefinition {
336    id: "clippy::too_many_arguments",
337    category: "maintainability",
338    producer: Producer::Clippy,
339    default_level: RuleLevel::Warn,
340    tier: RuleTier::P3,
341    help: "Group the related parameters into a struct so the signature names what it takes.",
342};
343pub(crate) static CLIPPY_TYPE_COMPLEXITY: RuleDefinition = RuleDefinition {
344    id: "clippy::type_complexity",
345    category: "maintainability",
346    producer: Producer::Clippy,
347    default_level: RuleLevel::Warn,
348    tier: RuleTier::P3,
349    help: "Name the nested type with a type alias or a dedicated struct so signatures say what they carry.",
350};
351pub(crate) static CLIPPY_UNIMPLEMENTED: RuleDefinition = RuleDefinition {
352    id: "clippy::unimplemented",
353    category: "correctness",
354    producer: Producer::Clippy,
355    default_level: RuleLevel::Warn,
356    tier: RuleTier::P1,
357    help: "Implement this code path or remove the reachable placeholder.",
358};
359pub(crate) static CLIPPY_UNNECESSARY_TO_OWNED: RuleDefinition = RuleDefinition {
360    id: "clippy::unnecessary_to_owned",
361    category: "performance",
362    producer: Producer::Clippy,
363    default_level: RuleLevel::Warn,
364    tier: RuleTier::P3,
365    help: "Pass the borrowed value directly; the callee never needs the owned copy.",
366};
367pub(crate) static CLIPPY_UNREACHABLE: RuleDefinition = RuleDefinition {
368    id: "clippy::unreachable",
369    category: "correctness",
370    producer: Producer::Clippy,
371    default_level: RuleLevel::Warn,
372    tier: RuleTier::P2,
373    help: "Make the remaining case explicit or return an error; an unreachable! that is reached aborts the process.",
374};
375pub(crate) static CLIPPY_UNUSED_ASYNC: RuleDefinition = RuleDefinition {
376    id: "clippy::unused_async",
377    category: "maintainability",
378    producer: Producer::Clippy,
379    default_level: RuleLevel::Warn,
380    tier: RuleTier::P3,
381    help: "Remove the async marker, or await the work the function was meant to drive.",
382};
383pub(crate) static CLIPPY_UNWRAP_USED: RuleDefinition = RuleDefinition {
384    id: "clippy::unwrap_used",
385    category: "reliability",
386    producer: Producer::Clippy,
387    default_level: RuleLevel::Warn,
388    tier: RuleTier::P3,
389    help: "Propagate the error with ? or provide a default instead of panicking on the absent value.",
390};
391pub(crate) static CLIPPY_USELESS_VEC: RuleDefinition = RuleDefinition {
392    id: "clippy::useless_vec",
393    category: "performance",
394    producer: Producer::Clippy,
395    default_level: RuleLevel::Warn,
396    tier: RuleTier::P3,
397    help: "Use an array or a slice literal; this value never needs a heap allocation.",
398};
399pub(crate) static CLIPPY_VEC_INIT_THEN_PUSH: RuleDefinition = RuleDefinition {
400    id: "clippy::vec_init_then_push",
401    category: "performance",
402    producer: Producer::Clippy,
403    default_level: RuleLevel::Warn,
404    tier: RuleTier::P3,
405    help: "Build the vector with the vec! literal so it is allocated once at its final size.",
406};
407pub(crate) static CLIPPY_ZOMBIE_PROCESSES: RuleDefinition = RuleDefinition {
408    id: "clippy::zombie_processes",
409    category: "reliability",
410    producer: Producer::Clippy,
411    default_level: RuleLevel::Warn,
412    tier: RuleTier::P2,
413    help: "Wait on the child process or otherwise reap it before the handle is dropped.",
414};
415pub(crate) static CARGO_DUPLICATE_MAJOR_VERSIONS: RuleDefinition = RuleDefinition {
416    id: "rust_doctor::cargo::duplicate_major_versions",
417    category: "dependencies",
418    producer: Producer::CargoHealth,
419    default_level: RuleLevel::Warn,
420    tier: RuleTier::P2,
421    help: "Align the requirements so one major version of the crate is resolved; duplicates ship twice and their types do not interoperate.",
422};
423pub(crate) static CARGO_MISSING_LOCKFILE: RuleDefinition = RuleDefinition {
424    id: "rust_doctor::cargo::missing_lockfile",
425    category: "dependencies",
426    producer: Producer::CargoHealth,
427    default_level: RuleLevel::Warn,
428    tier: RuleTier::P2,
429    help: "Commit Cargo.lock next to the manifest so every build of this binary resolves the same dependency versions.",
430};
431pub(crate) static CARGO_PERMISSIVE_LINT_TABLE: RuleDefinition = RuleDefinition {
432    id: "rust_doctor::cargo::permissive_lint_table",
433    category: "reliability",
434    producer: Producer::CargoHealth,
435    default_level: RuleLevel::Warn,
436    tier: RuleTier::P2,
437    help: "Remove the allow entry from [lints] and fix what it silences; a manifest-level allow hides the rule from every scan of this workspace.",
438};
439pub(crate) static CARGO_PERMISSIVE_RUSTFLAGS: RuleDefinition = RuleDefinition {
440    id: "rust_doctor::cargo::permissive_rustflags",
441    // Reliability at P2, like the manifest lint table: a flag that caps or
442    // silences lints neutralizes the scan itself for every build of the
443    // workspace, which is graver than weakening the shipped artifact.
444    category: "reliability",
445    producer: Producer::CargoHealth,
446    default_level: RuleLevel::Warn,
447    tier: RuleTier::P2,
448    help: "Remove the flag from .cargo/config.toml and fix what it silences; the closed list judged here is --cap-lints allow, -A warnings and -C overflow-checks=off, each of which disables a check for every build of this workspace.",
449};
450pub(crate) static CARGO_RELEASE_DEBUG_SYMBOLS: RuleDefinition = RuleDefinition {
451    id: "rust_doctor::cargo::release_debug_symbols",
452    category: "reliability",
453    producer: Producer::CargoHealth,
454    default_level: RuleLevel::Warn,
455    tier: RuleTier::P3,
456    help: "Set strip = \"symbols\" or remove debug from [profile.release]: full debug info ships absolute build paths inside the binary. Only [profile.release] itself is judged; profiles inheriting from it are not resolved.",
457};
458pub(crate) static CARGO_TEST_ONLY_DEPENDENCY: RuleDefinition = RuleDefinition {
459    id: "rust_doctor::cargo::test_only_dependency",
460    category: "dependencies",
461    producer: Producer::CargoHealth,
462    default_level: RuleLevel::Warn,
463    tier: RuleTier::P2,
464    help: "Move the entry to [dev-dependencies]: only the test suite references it, and every consumer of the library compiles it anyway.",
465};
466pub(crate) static CARGO_UNUSED_DEPENDENCY: RuleDefinition = RuleDefinition {
467    id: "rust_doctor::cargo::unused_dependency",
468    category: "dependencies",
469    producer: Producer::CargoHealth,
470    default_level: RuleLevel::Warn,
471    tier: RuleTier::P2,
472    help: "Remove the entry no source references, or switch this rule off with --rule or rust-doctor.toml for a crate needed for linking alone; references made only through macro expansion or doctests are not seen.",
473};
474pub(crate) static CARGO_UNCHECKED_RELEASE_OVERFLOW: RuleDefinition = RuleDefinition {
475    id: "rust_doctor::cargo::unchecked_release_overflow",
476    // The rule states a tradeoff, not a verdict: the Rust Performance Book
477    // measures overflow checks at a few percent on integer-heavy code, and the
478    // help names that cost so the reader can decline it.
479    category: "reliability",
480    producer: Producer::CargoHealth,
481    default_level: RuleLevel::Warn,
482    tier: RuleTier::P3,
483    help: "Set overflow-checks = true under [profile.release] so integer overflow panics instead of wrapping silently; the measured cost is a few percent on integer-heavy code, which is the tradeoff this finding asks you to decide.",
484};
485pub(crate) static CARGO_PATH_DEPENDENCY_OUTSIDE_WORKSPACE: RuleDefinition = RuleDefinition {
486    id: "rust_doctor::cargo::path_dependency_outside_workspace",
487    category: "dependencies",
488    producer: Producer::CargoHealth,
489    default_level: RuleLevel::Warn,
490    tier: RuleTier::P1,
491    help: "Vendor the crate inside the workspace or publish it; a path leaving the workspace only resolves on the author's machine.",
492};
493pub(crate) static CARGO_UNBOUNDED_REGISTRY: RuleDefinition = RuleDefinition {
494    id: "rust_doctor::cargo::unbounded_registry_dependency",
495    category: "reliability",
496    producer: Producer::CargoHealth,
497    default_level: RuleLevel::Warn,
498    tier: RuleTier::P3,
499    help: "Replace the unbounded version requirement with the minimum compatible version intended by the project.",
500};
501pub(crate) static CARGO_UNPINNED_GIT: RuleDefinition = RuleDefinition {
502    id: "rust_doctor::cargo::unpinned_git_dependency",
503    category: "security",
504    producer: Producer::CargoHealth,
505    default_level: RuleLevel::Warn,
506    tier: RuleTier::P1,
507    help: "Set rev to the full 40-character commit SHA intended by the project.",
508};
509pub(crate) static REPO_HARDCODED_CREDENTIAL: RuleDefinition = RuleDefinition {
510    id: "rust_doctor::repo::hardcoded_credential",
511    category: "security",
512    producer: Producer::Repo,
513    default_level: RuleLevel::Warn,
514    tier: RuleTier::P1,
515    help: "Remove the credential from the source and rotate it; the closed list judged here is AKIA, ghp_, github_pat_, sk-, xoxb- and BEGIN PRIVATE KEY blocks, and the matched value is never republished by the report.",
516};
517pub(crate) static REPO_TRACKED_SECRET_FILE: RuleDefinition = RuleDefinition {
518    id: "rust_doctor::repo::tracked_secret_file",
519    category: "security",
520    producer: Producer::Repo,
521    default_level: RuleLevel::Warn,
522    tier: RuleTier::P1,
523    help: "Remove the file from version control with git rm --cached, rotate what it contains, and add its name to .gitignore so it stays out; the report names the path and never its contents.",
524};
525pub(crate) static REPO_UNIGNORED_BUILD_OUTPUT: RuleDefinition = RuleDefinition {
526    id: "rust_doctor::repo::unignored_build_output",
527    category: "maintainability",
528    producer: Producer::Repo,
529    default_level: RuleLevel::Warn,
530    tier: RuleTier::P3,
531    help: "Add the target directory to .gitignore so build artifacts stay out of the repository; the directory judged is Cargo's, including a custom target-dir set in .cargo/config.toml, and any ignore source git honors counts.",
532};
533pub(crate) static SOURCE_DISABLED_TLS: RuleDefinition = RuleDefinition {
534    id: "rust_doctor::source::disabled_tls_verification",
535    category: "security",
536    producer: Producer::SourceKernel,
537    default_level: RuleLevel::Warn,
538    tier: RuleTier::P0,
539    help: "Keep TLS verification enabled and configure the required trust roots or server name instead.",
540};
541pub(crate) static SOURCE_DYNAMIC_SHELL: RuleDefinition = RuleDefinition {
542    id: "rust_doctor::source::dynamic_shell_command",
543    category: "security",
544    producer: Producer::SourceKernel,
545    default_level: RuleLevel::Warn,
546    tier: RuleTier::P0,
547    help: "Avoid the shell and pass values as separate Command arguments; otherwise apply shell-specific escaping at the trust boundary.",
548};
549pub(crate) static STRUCTURE_COMPLEX_FUNCTION: RuleDefinition = RuleDefinition {
550    id: "rust_doctor::structure::complex_function",
551    category: "maintainability",
552    producer: Producer::Structure,
553    default_level: RuleLevel::Warn,
554    tier: RuleTier::P3,
555    help: "Split the branching into smaller functions, or flatten it with early returns, so one reading holds the whole path.",
556};
557pub(crate) static STRUCTURE_CRATE_LEVEL_ALLOW: RuleDefinition = RuleDefinition {
558    id: "rust_doctor::structure::crate_level_allow",
559    // Reliability, like the manifest lint table: a file-wide allow neutralizes
560    // the scan for everything the file will ever contain, which is a different
561    // act from an untidy exemption on one item.
562    category: "reliability",
563    producer: Producer::Structure,
564    default_level: RuleLevel::Warn,
565    tier: RuleTier::P2,
566    help: "Scope the allow to the item that needs it; a file-wide or module-wide exemption, reasoned or not, also silences every future finding in its reach.",
567};
568pub(crate) static STRUCTURE_DUPLICATE_FUNCTION_BODY: RuleDefinition = RuleDefinition {
569    id: "rust_doctor::structure::duplicate_function_body",
570    category: "maintainability",
571    producer: Producer::Structure,
572    default_level: RuleLevel::Warn,
573    tier: RuleTier::P3,
574    help: "Keep one definition and call it from the other sites, or make what differs between them a parameter.",
575};
576pub(crate) static STRUCTURE_NEAR_DUPLICATE_FUNCTION_BODY: RuleDefinition = RuleDefinition {
577    id: "rust_doctor::structure::near_duplicate_function_body",
578    category: "maintainability",
579    producer: Producer::Structure,
580    default_level: RuleLevel::Warn,
581    tier: RuleTier::P3,
582    help: "Factor the shared shape into one function and pass what differs, or keep both and record why they must stay apart.",
583};
584pub(crate) static STRUCTURE_ORPHAN_MODULE_FILE: RuleDefinition = RuleDefinition {
585    id: "rust_doctor::structure::orphan_module_file",
586    category: "maintainability",
587    producer: Producer::Structure,
588    default_level: RuleLevel::Warn,
589    tier: RuleTier::P3,
590    help: "Declare the file with a mod declaration, or delete it: Cargo compiles no file the module tree does not reach.",
591};
592pub(crate) static STRUCTURE_OVERSIZED_UNIT: RuleDefinition = RuleDefinition {
593    id: "rust_doctor::structure::oversized_unit",
594    category: "maintainability",
595    producer: Producer::Structure,
596    default_level: RuleLevel::Warn,
597    tier: RuleTier::P3,
598    help: "Split the file, function, impl block or module along its responsibilities before growth makes the split harder.",
599};
600pub(crate) static STRUCTURE_STACKED_ALLOW: RuleDefinition = RuleDefinition {
601    id: "rust_doctor::structure::stacked_allow_attribute",
602    category: "maintainability",
603    producer: Producer::Structure,
604    default_level: RuleLevel::Warn,
605    tier: RuleTier::P3,
606    help: "Keep the one exemption the item actually needs and fix what the others hide; attributes produced by cfg_attr are not counted here.",
607};
608pub(crate) static STRUCTURE_UNREASONED_ALLOW: RuleDefinition = RuleDefinition {
609    id: "rust_doctor::structure::unreasoned_allow_attribute",
610    category: "maintainability",
611    producer: Producer::Structure,
612    default_level: RuleLevel::Warn,
613    tier: RuleTier::P3,
614    help: "Fix what the lint reports, or keep the allow and state why with reason = \"...\" so the exemption survives review.",
615};
616pub(crate) static STRUCTURE_UNREFERENCED_FEATURE: RuleDefinition = RuleDefinition {
617    id: "rust_doctor::structure::unreferenced_feature",
618    category: "maintainability",
619    producer: Producer::Structure,
620    default_level: RuleLevel::Warn,
621    tier: RuleTier::P3,
622    help: "Delete the feature nothing reads, or declare the one a cfg already gates; switch this rule off for a stub deliberately kept as published surface.",
623};
624
625pub(crate) const CATALOG: [&RuleDefinition; 62] = [
626    &CLIPPY_ARC_WITH_NON_SEND_SYNC,
627    &CLIPPY_AWAIT_HOLDING_LOCK,
628    &CLIPPY_AWAIT_HOLDING_REFCELL_REF,
629    &CLIPPY_DBG_MACRO,
630    &CLIPPY_EXIT,
631    &CLIPPY_EXPECT_USED,
632    &CLIPPY_FORMAT_COLLECT,
633    &CLIPPY_INDEXING_SLICING,
634    &CLIPPY_LARGE_TYPES_PASSED_BY_VALUE,
635    &CLIPPY_MANUAL_MEMCPY,
636    &CLIPPY_MEM_FORGET,
637    &CLIPPY_MISSING_SAFETY_DOC,
638    &CLIPPY_MUT_MUTEX_LOCK,
639    &CLIPPY_NON_SEND_FIELDS_IN_SEND_TY,
640    &CLIPPY_PANIC,
641    &CLIPPY_PANIC_IN_RESULT_FN,
642    &CLIPPY_PERMISSIONS_SET_READONLY_FALSE,
643    &CLIPPY_PRINT_STDERR,
644    &CLIPPY_PRINT_STDOUT,
645    &CLIPPY_PTR_ARG,
646    &CLIPPY_RC_BUFFER,
647    &CLIPPY_RC_MUTEX,
648    &CLIPPY_REDUNDANT_ALLOCATION,
649    &CLIPPY_STABLE_SORT_PRIMITIVE,
650    &CLIPPY_STRING_SLICE,
651    &CLIPPY_SUSPICIOUS_COMMAND_ARG_SPACE,
652    &CLIPPY_TODO,
653    &CLIPPY_TOO_MANY_ARGUMENTS,
654    &CLIPPY_TYPE_COMPLEXITY,
655    &CLIPPY_UNIMPLEMENTED,
656    &CLIPPY_UNNECESSARY_TO_OWNED,
657    &CLIPPY_UNREACHABLE,
658    &CLIPPY_UNUSED_ASYNC,
659    &CLIPPY_UNWRAP_USED,
660    &CLIPPY_USELESS_VEC,
661    &CLIPPY_VEC_INIT_THEN_PUSH,
662    &CLIPPY_ZOMBIE_PROCESSES,
663    &CARGO_DUPLICATE_MAJOR_VERSIONS,
664    &CARGO_MISSING_LOCKFILE,
665    &CARGO_PATH_DEPENDENCY_OUTSIDE_WORKSPACE,
666    &CARGO_PERMISSIVE_LINT_TABLE,
667    &CARGO_PERMISSIVE_RUSTFLAGS,
668    &CARGO_RELEASE_DEBUG_SYMBOLS,
669    &CARGO_TEST_ONLY_DEPENDENCY,
670    &CARGO_UNBOUNDED_REGISTRY,
671    &CARGO_UNCHECKED_RELEASE_OVERFLOW,
672    &CARGO_UNPINNED_GIT,
673    &CARGO_UNUSED_DEPENDENCY,
674    &REPO_HARDCODED_CREDENTIAL,
675    &REPO_TRACKED_SECRET_FILE,
676    &REPO_UNIGNORED_BUILD_OUTPUT,
677    &SOURCE_DISABLED_TLS,
678    &SOURCE_DYNAMIC_SHELL,
679    &STRUCTURE_COMPLEX_FUNCTION,
680    &STRUCTURE_CRATE_LEVEL_ALLOW,
681    &STRUCTURE_DUPLICATE_FUNCTION_BODY,
682    &STRUCTURE_NEAR_DUPLICATE_FUNCTION_BODY,
683    &STRUCTURE_ORPHAN_MODULE_FILE,
684    &STRUCTURE_OVERSIZED_UNIT,
685    &STRUCTURE_STACKED_ALLOW,
686    &STRUCTURE_UNREASONED_ALLOW,
687    &STRUCTURE_UNREFERENCED_FEATURE,
688];
689
690pub(crate) fn find(id: &str) -> Option<&'static RuleDefinition> {
691    find_in(&CATALOG, id)
692}
693
694pub(super) fn find_in<'a>(catalog: &'a [&RuleDefinition], id: &str) -> Option<&'a RuleDefinition> {
695    super::by_id(catalog, id, |definition| definition.id).copied()
696}