#![allow(dead_code)]
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::Path;
pub type InlineAllows = HashMap<(String, u32), HashSet<String>>;
fn parse_file_allows(content: &str) -> HashMap<u32, HashSet<String>> {
let mut allows: HashMap<u32, HashSet<String>> = HashMap::new();
for (idx, line) in content.lines().enumerate() {
let line_num = (idx + 1) as u32;
if let Some(start) = line.find("// jonesy:allow(") {
let rest = &line[start + 16..]; if let Some(end) = rest.find(')') {
let causes_str = &rest[..end];
let causes: HashSet<String> = causes_str
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if !causes.is_empty() {
allows.insert(line_num, causes);
}
}
}
}
allows
}
pub fn scan_inline_allows(file_paths: &[&str]) -> InlineAllows {
let mut all_allows = InlineAllows::new();
for file_path in file_paths {
if let Ok(content) = fs::read_to_string(file_path) {
let file_allows = parse_file_allows(&content);
for (line, causes) in file_allows {
all_allows.insert((file_path.to_string(), line), causes);
}
}
}
all_allows
}
pub fn scan_file_allows(file_path: &str) -> HashMap<u32, HashSet<String>> {
if let Ok(content) = fs::read_to_string(file_path) {
parse_file_allows(&content)
} else {
HashMap::new()
}
}
pub fn is_allowed_by_inline(
allows: &InlineAllows,
file_path: &str,
line: u32,
cause_id: &str,
) -> bool {
let key = (file_path.to_string(), line);
if let Some(causes) = allows.get(&key) {
if causes.contains("*") || causes.contains(cause_id) {
return true;
}
}
if line > 1 {
let prev_key = (file_path.to_string(), line - 1);
if let Some(causes) = allows.get(&prev_key) {
if causes.contains("*") || causes.contains(cause_id) {
return true;
}
}
}
false
}
pub fn check_inline_allow(
file_path: &str,
line: u32,
cause_id: &str,
workspace_root: Option<&Path>,
) -> bool {
let content = read_source_file_with_root(file_path, workspace_root);
let content = match content {
Some(c) => c,
None => return false, };
let lines: Vec<&str> = content.lines().collect();
let start_line = line.saturating_sub(2);
let end_line = line.saturating_add(2);
for check_line in start_line..=end_line {
if let Some(line_content) = lines.get((check_line as usize).saturating_sub(1)) {
if let Some(causes) = parse_line_allows(line_content) {
if causes.contains("*") || causes.contains(cause_id) {
return true;
}
}
}
}
false
}
fn read_source_file_with_root(file_path: &str, workspace_root: Option<&Path>) -> Option<String> {
let path = Path::new(file_path);
if path.exists() {
return fs::read_to_string(path).ok();
}
if let Some(root) = workspace_root {
let candidate = root.join(file_path);
if candidate.exists() {
return fs::read_to_string(&candidate).ok();
}
}
if let Ok(cwd) = std::env::current_dir() {
let mut dir = cwd.as_path();
loop {
let candidate = dir.join(file_path);
if candidate.exists() {
return fs::read_to_string(&candidate).ok();
}
let cargo_toml = dir.join("Cargo.toml");
if cargo_toml.exists() {
if let Ok(content) = fs::read_to_string(&cargo_toml) {
if content.contains("[workspace]") {
let candidate = dir.join(file_path);
if candidate.exists() {
return fs::read_to_string(&candidate).ok();
}
break; }
}
}
match dir.parent() {
Some(parent) => dir = parent,
None => break,
}
}
}
None
}
fn parse_line_allows(line: &str) -> Option<HashSet<String>> {
if let Some(start) = line.find("// jonesy:allow(") {
let rest = &line[start + 16..];
if let Some(end) = rest.find(')') {
let causes_str = &rest[..end];
let causes: HashSet<String> = causes_str
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if !causes.is_empty() {
return Some(causes);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_single_cause() {
let content = r#"
fn foo() {
let x = None.unwrap(); // jonesy:allow(unwrap)
}
"#;
let allows = parse_file_allows(content);
assert!(allows.get(&3).unwrap().contains("unwrap"));
}
#[test]
fn test_parse_multiple_causes() {
let content = r#"
fn foo() {
something(); // jonesy:allow(unwrap, expect, panic)
}
"#;
let allows = parse_file_allows(content);
let causes = allows.get(&3).unwrap();
assert!(causes.contains("unwrap"));
assert!(causes.contains("expect"));
assert!(causes.contains("panic"));
}
#[test]
fn test_parse_wildcard() {
let content = r#"
fn foo() {
dangerous(); // jonesy:allow(*)
}
"#;
let allows = parse_file_allows(content);
assert!(allows.get(&3).unwrap().contains("*"));
}
#[test]
fn test_parse_line_allows() {
let line = " let x = foo(); // jonesy:allow(unwrap, bounds)";
let causes = parse_line_allows(line).unwrap();
assert!(causes.contains("unwrap"));
assert!(causes.contains("bounds"));
}
#[test]
fn test_parse_line_allows_no_match() {
let line = " let x = foo(); // regular comment";
assert!(parse_line_allows(line).is_none());
}
#[test]
fn test_parse_line_allows_empty() {
let line = " let x = foo(); // jonesy:allow()";
assert!(parse_line_allows(line).is_none());
}
#[test]
fn test_parse_file_allows_no_comments() {
let content = "fn foo() {\n bar();\n}\n";
let allows = parse_file_allows(content);
assert!(allows.is_empty());
}
#[test]
fn test_parse_file_allows_multiple_lines() {
let content = r#"
fn foo() {
bar(); // jonesy:allow(panic)
baz();
qux(); // jonesy:allow(unwrap)
}
"#;
let allows = parse_file_allows(content);
assert_eq!(allows.len(), 2);
assert!(allows.get(&3).unwrap().contains("panic"));
assert!(allows.get(&5).unwrap().contains("unwrap"));
}
#[test]
fn test_is_allowed_by_inline_exact_match() {
let mut allows = InlineAllows::new();
let mut causes = HashSet::new();
causes.insert("unwrap".to_string());
allows.insert(("test.rs".to_string(), 10), causes);
assert!(is_allowed_by_inline(&allows, "test.rs", 10, "unwrap"));
assert!(!is_allowed_by_inline(&allows, "test.rs", 10, "panic"));
}
#[test]
fn test_is_allowed_by_inline_wildcard() {
let mut allows = InlineAllows::new();
let mut causes = HashSet::new();
causes.insert("*".to_string());
allows.insert(("test.rs".to_string(), 10), causes);
assert!(is_allowed_by_inline(&allows, "test.rs", 10, "unwrap"));
assert!(is_allowed_by_inline(&allows, "test.rs", 10, "panic"));
assert!(is_allowed_by_inline(&allows, "test.rs", 10, "anything"));
}
#[test]
fn test_is_allowed_by_inline_previous_line() {
let mut allows = InlineAllows::new();
let mut causes = HashSet::new();
causes.insert("unwrap".to_string());
allows.insert(("test.rs".to_string(), 9), causes);
assert!(is_allowed_by_inline(&allows, "test.rs", 10, "unwrap"));
assert!(!is_allowed_by_inline(&allows, "test.rs", 11, "unwrap"));
}
#[test]
fn test_is_allowed_by_inline_no_match() {
let allows = InlineAllows::new();
assert!(!is_allowed_by_inline(&allows, "test.rs", 10, "unwrap"));
}
#[test]
fn test_is_allowed_by_inline_line_one() {
let mut allows = InlineAllows::new();
let mut causes = HashSet::new();
causes.insert("unwrap".to_string());
allows.insert(("test.rs".to_string(), 1), causes);
assert!(is_allowed_by_inline(&allows, "test.rs", 1, "unwrap"));
}
#[test]
fn test_scan_file_allows_nonexistent() {
let allows = scan_file_allows("/nonexistent/path/to/file.rs");
assert!(allows.is_empty());
}
#[test]
fn test_scan_file_allows_with_content() {
use std::io::Write;
let temp_dir = tempfile::TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.rs");
let mut file = std::fs::File::create(&file_path).unwrap();
writeln!(file, "fn foo() {{").unwrap();
writeln!(file, " bar(); // jonesy:allow(panic)").unwrap();
writeln!(file, "}}").unwrap();
let allows = scan_file_allows(file_path.to_str().unwrap());
assert_eq!(allows.len(), 1);
assert!(allows.get(&2).unwrap().contains("panic"));
}
#[test]
fn test_scan_inline_allows_multiple_files() {
use std::io::Write;
let temp_dir = tempfile::TempDir::new().unwrap();
let file1 = temp_dir.path().join("file1.rs");
let mut f1 = std::fs::File::create(&file1).unwrap();
writeln!(f1, "foo(); // jonesy:allow(unwrap)").unwrap();
let file2 = temp_dir.path().join("file2.rs");
let mut f2 = std::fs::File::create(&file2).unwrap();
writeln!(f2, "bar(); // jonesy:allow(panic)").unwrap();
let paths = [file1.to_str().unwrap(), file2.to_str().unwrap()];
let allows = scan_inline_allows(&paths);
assert_eq!(allows.len(), 2);
}
#[test]
fn test_scan_inline_allows_nonexistent_file_skipped() {
let paths = ["/nonexistent/file.rs"];
let allows = scan_inline_allows(&paths);
assert!(allows.is_empty());
}
#[test]
fn test_check_inline_allow_with_file() {
use std::io::Write;
let temp_dir = tempfile::TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.rs");
let mut file = std::fs::File::create(&file_path).unwrap();
writeln!(file, "fn foo() {{").unwrap();
writeln!(file, " bar(); // jonesy:allow(unwrap)").unwrap();
writeln!(file, "}}").unwrap();
assert!(check_inline_allow(
file_path.to_str().unwrap(),
2,
"unwrap",
None
));
assert!(check_inline_allow(
file_path.to_str().unwrap(),
3,
"unwrap",
None
));
assert!(!check_inline_allow(
file_path.to_str().unwrap(),
2,
"panic",
None
));
}
#[test]
fn test_check_inline_allow_absolute_path() {
use std::io::Write;
let temp_dir = tempfile::TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.rs");
let mut file = std::fs::File::create(&file_path).unwrap();
writeln!(file, "x(); // jonesy:allow(*)").unwrap();
drop(file);
let result = check_inline_allow(file_path.to_str().unwrap(), 1, "anything", None);
assert!(result);
}
#[test]
fn test_check_inline_allow_nonexistent_file() {
assert!(!check_inline_allow(
"/nonexistent/file.rs",
1,
"unwrap",
None
));
}
#[test]
fn test_check_inline_allow_wildcard() {
use std::io::Write;
let temp_dir = tempfile::TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.rs");
let mut file = std::fs::File::create(&file_path).unwrap();
writeln!(file, "dangerous(); // jonesy:allow(*)").unwrap();
assert!(check_inline_allow(
file_path.to_str().unwrap(),
1,
"anything",
None
));
assert!(check_inline_allow(
file_path.to_str().unwrap(),
1,
"unwrap",
None
));
}
#[test]
fn test_is_allowed_by_inline_wrong_cause() {
let mut allows = InlineAllows::new();
let mut causes = HashSet::new();
causes.insert("unwrap".to_string());
allows.insert(("test.rs".to_string(), 10), causes);
assert!(!is_allowed_by_inline(&allows, "test.rs", 10, "panic"));
}
#[test]
fn test_is_allowed_by_inline_prev_line_wildcard() {
let mut allows = InlineAllows::new();
let mut causes = HashSet::new();
causes.insert("*".to_string());
allows.insert(("test.rs".to_string(), 9), causes);
assert!(is_allowed_by_inline(&allows, "test.rs", 10, "anything"));
}
}