diff --git a/.cspell.json b/.cspell.json index 556432d69a4..a2856029c2c 100644 --- a/.cspell.json +++ b/.cspell.json @@ -22,7 +22,7 @@ "src/intrinsic/llvm.rs" ], "ignoreRegExpList": [ - "/(FIXME|NOTE|TODO)\\([^)]+\\)/", + "/(FIXME|NOTE)\\([^)]+\\)/", "__builtin_\\w*" ] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 840c09409bb..77c1b997df5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,6 +88,9 @@ jobs: - name: Check formatting run: ./y.sh fmt --check + - name: Check todo + run: ./y.sh check-todo + - name: clippy run: | cargo clippy --all-targets -- -D warnings diff --git a/build_system/src/main.rs b/build_system/src/main.rs index ae975c94fff..150239cbbb4 100644 --- a/build_system/src/main.rs +++ b/build_system/src/main.rs @@ -12,6 +12,7 @@ mod prepare; mod rust_tools; mod rustc_info; mod test; +mod todo; mod utils; const BUILD_DIR: &str = "build"; @@ -45,7 +46,8 @@ Commands: clone-gcc : Clones the GCC compiler from a specified source. fmt : Runs rustfmt fuzz : Fuzzes `cg_gcc` using rustlantis - abi-test : Runs the abi-cafe test suite on the codegen, checking for ABI compatibility with LLVM" + abi-test : Runs the abi-cafe test suite on the codegen, checking for ABI compatibility with LLVM + check-todo : Checks todo in the project" ); } @@ -61,6 +63,7 @@ pub enum Command { Fmt, Fuzz, AbiTest, + CheckTodo, } fn main() { @@ -80,6 +83,7 @@ fn main() { Some("info") => Command::Info, Some("clone-gcc") => Command::CloneGcc, Some("abi-test") => Command::AbiTest, + Some("check-todo") => Command::CheckTodo, Some("fmt") => Command::Fmt, Some("fuzz") => Command::Fuzz, Some("--help") => { @@ -106,6 +110,7 @@ fn main() { Command::Fmt => fmt::run(), Command::Fuzz => fuzz::run(), Command::AbiTest => abi_test::run(), + Command::CheckTodo => todo::run(), } { eprintln!("Command failed to run: {e}"); process::exit(1); diff --git a/build_system/src/todo.rs b/build_system/src/todo.rs new file mode 100644 index 00000000000..74b5c9ea478 --- /dev/null +++ b/build_system/src/todo.rs @@ -0,0 +1,87 @@ +use std::ffi::OsStr; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +const EXTENSIONS: &[&str] = + &["rs", "py", "js", "sh", "c", "cpp", "h", "md", "css", "ftl", "toml", "yml", "yaml"]; +const SKIP_FILES: &[&str] = &["build_system/src/todo.rs", ".github/workflows/ci.yml"]; + +fn has_supported_extension(path: &Path) -> bool { + path.extension().is_some_and(|ext| EXTENSIONS.iter().any(|e| ext == OsStr::new(e))) +} + +fn is_editor_temp(path: &Path) -> bool { + path.file_name().is_some_and(|name| name.to_string_lossy().starts_with(".#")) +} + +fn list_tracked_files() -> Result, String> { + let output = Command::new("git") + .args(["ls-files", "-z"]) + .output() + .map_err(|e| format!("Failed to run `git ls-files`: {e}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("`git ls-files` failed: {stderr}")); + } + + let mut files = Vec::new(); + for entry in output.stdout.split(|b| *b == 0) { + if entry.is_empty() { + continue; + } + let path = std::str::from_utf8(entry) + .map_err(|_| "Non-utf8 path returned by `git ls-files`".to_string())?; + files.push(PathBuf::from(path)); + } + + Ok(files) +} + +pub(crate) fn run() -> Result<(), String> { + let files = list_tracked_files()?; + let mut errors = Vec::new(); + // Avoid embedding the task marker in source so greps only find real occurrences. + let todo_marker = "todo".to_ascii_uppercase(); + + for file in files { + if is_editor_temp(&file) { + continue; + } + if SKIP_FILES.iter().any(|skip| file.ends_with(Path::new(skip))) { + continue; + } + if !has_supported_extension(&file) { + continue; + } + + let bytes = + fs::read(&file).map_err(|e| format!("Failed to read `{}`: {e}", file.display()))?; + let Ok(contents) = std::str::from_utf8(&bytes) else { + continue; + }; + + for (i, line) in contents.split('\n').enumerate() { + let trimmed = line.trim(); + if trimmed.contains(&todo_marker) { + errors.push(format!( + "{}:{}: {} is used for tasks that should be done before merging a PR; if you want to leave a message in the codebase use FIXME", + file.display(), + i + 1, + todo_marker + )); + } + } + } + + if errors.is_empty() { + return Ok(()); + } + + for err in &errors { + eprintln!("{err}"); + } + + Err(format!("found {} {}(s)", errors.len(), todo_marker)) +}