mod eval_patterns;
mod network;
mod package_json;
mod regex_util;
mod vscode;
use std::path::Path;
use crate::Result;
use crate::findings::Findings;
pub fn scan_files(project_root: &Path, files: &[String]) -> Result<Findings> {
let mut findings = Findings::new();
for rel in files {
let path = std::path::PathBuf::from(rel);
let abs = project_root.join(&path);
let bytes = match std::fs::read(&abs) {
Ok(b) => b,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
Err(source) => return Err(crate::Error::Io { path: abs, source }),
};
let body = match std::str::from_utf8(&bytes) {
Ok(s) => s,
Err(_) => continue,
};
findings.extend(checks_for(&path, body));
}
Ok(findings)
}
fn checks_for(path: &Path, body: &str) -> Vec<crate::findings::Finding> {
let mut out = Vec::new();
let file_name = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
if file_name == "tasks.json" && contains_segment(path, ".vscode") {
out.extend(vscode::check_tasks_json(path, body));
}
if file_name == "package.json" {
out.extend(package_json::check(path, body));
}
if is_jsish(file_name) {
out.extend(eval_patterns::check(path, body));
out.extend(network::check(path, body));
}
out
}
fn is_jsish(file_name: &str) -> bool {
matches!(
std::path::Path::new(file_name)
.extension()
.and_then(|e| e.to_str()),
Some("js") | Some("mjs") | Some("cjs") | Some("ts") | Some("tsx") | Some("jsx")
)
}
fn contains_segment(path: &Path, needle: &str) -> bool {
path.components()
.any(|c| c.as_os_str().to_string_lossy() == needle)
}
#[cfg(test)]
mod tests {
use super::*;
type TestResult = std::result::Result<(), Box<dyn std::error::Error>>;
#[test]
fn checks_for_dispatches_only_to_applicable_modules() {
let body = "console.log(1)";
let out = checks_for(Path::new("src/index.ts"), body);
assert!(
out.iter()
.all(|f| !f.rule_id.starts_with("heuristics/package_json"))
);
}
#[test]
fn scan_files_skips_binary() -> TestResult {
let tmp = tempfile::tempdir()?;
let path = tmp.path().join("blob.js");
std::fs::write(&path, [0xff, 0xfe, 0xfd, 0xfc])?;
let f = scan_files(tmp.path(), &["blob.js".into()])?;
assert!(f.is_empty());
Ok(())
}
#[test]
fn jsish_extensions_recognized() {
assert!(is_jsish("a.js"));
assert!(is_jsish("a.mjs"));
assert!(is_jsish("a.cjs"));
assert!(is_jsish("a.ts"));
assert!(is_jsish("a.tsx"));
assert!(is_jsish("a.jsx"));
assert!(!is_jsish("a.json"));
assert!(!is_jsish("a.txt"));
}
}