tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit 6413ac4a57348ddf8d57da3079137eea5db7335b
parent c57445a0c95945e80637eac70ff3df0b712b82f3
Author: triesap <tyson@radroots.org>
Date:   Sat,  6 Jun 2026 14:01:30 -0700

quality: forbid source comments

- add a Rust source scanner that rejects line and block comments
- keep URLs, raw strings, regular strings, and char literals allowed
- run the guard from the workspace check script
- fix the clippy warning exposed by the full check lane

Diffstat:
Acrates/tangle/tests/source_comments.rs | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/tangle_nips/src/lib.rs | 2+-
Mscripts/check.sh | 1+
3 files changed, 203 insertions(+), 1 deletion(-)

diff --git a/crates/tangle/tests/source_comments.rs b/crates/tangle/tests/source_comments.rs @@ -0,0 +1,201 @@ +#![forbid(unsafe_code)] + +use std::fs; +use std::path::{Path, PathBuf}; + +#[test] +fn rust_source_files_do_not_contain_comments() { + let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .expect("workspace root"); + let mut files = rust_files(&workspace_root.join("crates")); + files.sort(); + let mut violations = Vec::new(); + for path in files { + let source = fs::read_to_string(&path).expect("source file"); + for offset in comment_offsets(&source) { + let (line, column) = line_column(&source, offset); + violations.push(format!( + "{}:{line}:{column}", + path.strip_prefix(workspace_root) + .expect("relative path") + .display() + )); + } + } + assert!( + violations.is_empty(), + "source comments are forbidden:\n{}", + violations.join("\n") + ); +} + +#[test] +fn scanner_detects_comments_without_matching_string_content() { + assert_eq!( + comment_offsets(r#"let text = "https://relay.test/path";"#), + [] + ); + assert_eq!( + comment_offsets(r##"let text = r#"// not a comment"#;"##), + [] + ); + assert_eq!(comment_offsets("let slash = '/';"), []); + assert_eq!(comment_offsets("let value = 1; // comment"), vec![15]); + assert_eq!(comment_offsets("let value = /* comment */ 1;"), vec![12]); +} + +fn rust_files(root: &Path) -> Vec<PathBuf> { + let mut files = Vec::new(); + collect_rust_files(root, &mut files); + files +} + +fn collect_rust_files(path: &Path, files: &mut Vec<PathBuf>) { + for entry in fs::read_dir(path).expect("source directory") { + let path = entry.expect("source entry").path(); + if path.is_dir() { + collect_rust_files(&path, files); + } else if path.extension().is_some_and(|extension| extension == "rs") { + files.push(path); + } + } +} + +fn comment_offsets(source: &str) -> Vec<usize> { + let bytes = source.as_bytes(); + let mut offsets = Vec::new(); + let mut index = 0; + while index < bytes.len() { + if let Some(end) = raw_string_end(bytes, index) { + index = end; + continue; + } + if let Some(end) = regular_string_end(bytes, index) { + index = end; + continue; + } + if let Some(end) = char_literal_end(bytes, index) { + index = end; + continue; + } + if bytes[index] == b'/' && index + 1 < bytes.len() { + match bytes[index + 1] { + b'/' => { + offsets.push(index); + index = line_comment_end(bytes, index + 2); + continue; + } + b'*' => { + offsets.push(index); + index = block_comment_end(bytes, index + 2); + continue; + } + _ => {} + } + } + index += 1; + } + offsets +} + +fn raw_string_end(bytes: &[u8], index: usize) -> Option<usize> { + let mut cursor = index; + if matches!(bytes.get(cursor), Some(b'b' | b'c')) { + cursor += 1; + } + if bytes.get(cursor) != Some(&b'r') { + return None; + } + cursor += 1; + let mut hashes = 0; + while bytes.get(cursor) == Some(&b'#') { + hashes += 1; + cursor += 1; + } + if bytes.get(cursor) != Some(&b'"') { + return None; + } + cursor += 1; + while cursor < bytes.len() { + if bytes[cursor] == b'"' + && bytes + .get(cursor + 1..cursor + 1 + hashes) + .is_some_and(|suffix| suffix.iter().all(|byte| *byte == b'#')) + { + return Some(cursor + 1 + hashes); + } + cursor += 1; + } + Some(bytes.len()) +} + +fn regular_string_end(bytes: &[u8], index: usize) -> Option<usize> { + let quote = if matches!(bytes.get(index), Some(b'b' | b'c')) { + index + 1 + } else { + index + }; + if bytes.get(quote) != Some(&b'"') { + return None; + } + let mut cursor = quote + 1; + while cursor < bytes.len() { + match bytes[cursor] { + b'\\' => cursor += 2, + b'"' => return Some(cursor + 1), + _ => cursor += 1, + } + } + Some(bytes.len()) +} + +fn char_literal_end(bytes: &[u8], index: usize) -> Option<usize> { + if bytes.get(index) != Some(&b'\'') { + return None; + } + let mut cursor = index + 1; + while cursor < bytes.len() && bytes[cursor] != b'\n' { + match bytes[cursor] { + b'\\' => cursor += 2, + b'\'' => return Some(cursor + 1), + _ => cursor += 1, + } + } + None +} + +fn line_comment_end(bytes: &[u8], mut index: usize) -> usize { + while index < bytes.len() && bytes[index] != b'\n' { + index += 1; + } + index +} + +fn block_comment_end(bytes: &[u8], mut index: usize) -> usize { + while index + 1 < bytes.len() { + if bytes[index] == b'*' && bytes[index + 1] == b'/' { + return index + 2; + } + index += 1; + } + bytes.len() +} + +fn line_column(source: &str, offset: usize) -> (usize, usize) { + let mut line = 1; + let mut column = 1; + for (index, character) in source.char_indices() { + if index == offset { + return (line, column); + } + if character == '\n' { + line += 1; + column = 1; + } else { + column += 1; + } + } + (line, column) +} diff --git a/crates/tangle_nips/src/lib.rs b/crates/tangle_nips/src/lib.rs @@ -2453,7 +2453,7 @@ mod tests { comment.parent().author().expect("parent author").as_str(), parent_pubkey ); - assert_eq!(comment.cited_events(), &[comment_event.clone()]); + assert_eq!(comment.cited_events(), std::slice::from_ref(&comment_event)); assert_eq!(comment.mentioned_pubkeys()[0].as_str(), parent_pubkey); assert_eq!(comment.mentioned_pubkeys()[1].as_str(), mentioned_pubkey); match comment.root().target() { diff --git a/scripts/check.sh b/scripts/check.sh @@ -2,5 +2,6 @@ set -euo pipefail cargo fmt --all -- --check +cargo test -p tangle --test source_comments cargo check --workspace --all-targets cargo clippy --workspace --all-targets -- -D warnings