// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use regex::RegexBuilder; use crate::common::{self, LintIssue}; pub fn run( pattern: &str, ignore_case: bool, linter: &str, message: &str, rule: &str, ) -> Result<(), String> { let re = RegexBuilder::new(pattern) .case_insensitive(ignore_case) .build() .map_err(|e| format!("Invalid regex pattern '{pattern}': {e}"))?; let paths = common::read_paths_from_stdin(); common::par_map_lint(&paths, |path| { run_lint_in_parallel(path, &re, linter, message, rule) }); Ok(()) } pub fn run_lint_in_parallel( path: &str, re: ®ex::Regex, linter: &str, message: &str, rule: &str, ) -> Vec { let content = match std::fs::read_to_string(path) { Ok(c) => c, Err(e) => { eprintln!("Warning: could not read {path}: {e}"); return Vec::new(); } }; let mut issues = Vec::new(); for (lineno, line) in content.lines().enumerate() { for m in re.find_iter(line) { issues.push(LintIssue { path: path.to_string(), lineno: Some(lineno + 1), column: Some(m.start() + 1), message: message.to_string(), level: "error".to_string(), linter: linter.to_string(), rule: Some(rule.to_string()), }); } } issues } #[cfg(test)] mod tests { use super::*; use std::io::Write; #[test] fn test_run_lint_in_parallel_finds_matches() { let dir = tempfile::tempdir().unwrap(); let file_path = dir.path().join("test.txt"); { let mut f = std::fs::File::create(&file_path).unwrap(); writeln!(f, "this is a blacklist word").unwrap(); writeln!(f, "nothing here").unwrap(); writeln!(f, "whitelist too").unwrap(); } let re = RegexBuilder::new(r"(black|white)[-_]?list") .case_insensitive(true) .build() .unwrap(); let issues = run_lint_in_parallel( file_path.to_str().unwrap(), &re, "test-linter", "bad word", "test-rule", ); assert_eq!(issues.len(), 2); assert_eq!(issues[0].lineno, Some(1)); assert_eq!(issues[0].column, Some(11)); assert_eq!(issues[1].lineno, Some(3)); assert_eq!(issues[1].column, Some(1)); } #[test] fn test_run_lint_in_parallel_no_matches() { let dir = tempfile::tempdir().unwrap(); let file_path = dir.path().join("clean.txt"); std::fs::write(&file_path, "nothing bad here\n").unwrap(); let re = RegexBuilder::new(r"(black|white)[-_]?list") .case_insensitive(true) .build() .unwrap(); let issues = run_lint_in_parallel( file_path.to_str().unwrap(), &re, "test-linter", "bad word", "test-rule", ); assert!(issues.is_empty()); } #[test] fn test_run_lint_in_parallel_missing_file() { let re = RegexBuilder::new(r"te.t").build().unwrap(); let issues = run_lint_in_parallel("/nonexistent/path.txt", &re, "test-linter", "msg", "rule"); assert!(issues.is_empty()); } }