1pub mod crate_index;
24pub mod diagnostic;
25pub mod helpers;
26pub mod lint_code;
27pub mod rules;
28
29use std::env;
30use std::path::{Path, PathBuf};
31
32pub use crate_index::{CrateIndex, LintItem, LintKind};
33pub use diagnostic::{Diagnostic, Severity};
34pub use lint_code::LintCode;
35
36fn cargo_warning(message: &str) {
38 let message = message.replace(['\n', '\r'], " ");
39 println!("cargo::warning={}", message.trim());
40}
41
42pub fn build_script() {
47 println!("cargo::rerun-if-env-changed=MINIEXTENDR_LINT");
48
49 let enabled = match lint_enabled("MINIEXTENDR_LINT") {
50 Ok(enabled) => enabled,
51 Err(message) => {
52 cargo_warning(&message);
53 return;
54 }
55 };
56
57 if !enabled {
58 return;
59 }
60
61 let manifest_dir = match env::var("CARGO_MANIFEST_DIR") {
62 Ok(dir) => PathBuf::from(dir),
63 Err(err) => {
64 cargo_warning(&format!("CARGO_MANIFEST_DIR: {err}"));
65 return;
66 }
67 };
68
69 let report = match run(&manifest_dir) {
70 Ok(report) => report,
71 Err(message) => {
72 cargo_warning(&message);
73 return;
74 }
75 };
76
77 for path in &report.files {
78 println!("cargo::rerun-if-changed={}", path.display());
79 }
80
81 if !report.diagnostics.is_empty() {
82 cargo_warning("miniextendr-lint found issues");
83 for diag in &report.diagnostics {
84 cargo_warning(&diag.to_string());
85 }
86 }
87
88 }
92
93#[derive(Debug, Default)]
94pub struct LintReport {
96 pub files: Vec<PathBuf>,
98 pub diagnostics: Vec<Diagnostic>,
100 pub errors: Vec<String>,
102}
103
104pub fn lint_enabled(env_var: &str) -> Result<bool, String> {
108 match env::var(env_var) {
109 Ok(value) => {
110 let normalized = value.trim().to_ascii_lowercase();
111 match normalized.as_str() {
112 "0" | "false" | "no" | "off" | "" => Ok(false),
113 "1" | "true" | "yes" | "on" => Ok(true),
114 _ => Err(format!(
115 "{env_var} has invalid value '{value}'; use 1/0, true/false, yes/no, on/off"
116 )),
117 }
118 }
119 Err(env::VarError::NotPresent) => Ok(true),
120 Err(err) => Err(format!("{env_var}: {err}")),
121 }
122}
123
124pub fn run(root: impl AsRef<Path>) -> Result<LintReport, String> {
128 let root = root.as_ref();
129
130 let index = CrateIndex::build(root)?;
131 let diagnostics = rules::run_all_rules(&index);
132
133 let errors = diagnostics
134 .iter()
135 .filter(|d| d.severity == Severity::Error)
136 .map(|d| d.to_legacy_string())
137 .collect();
138
139 Ok(LintReport {
140 files: index.files,
141 diagnostics,
142 errors,
143 })
144}