use crate::lang::Lang;
use crate::mutate::mutations::{get_mutation, MutationPoint};
use anyhow::{Context, Result};
use std::fs;
use std::path::Path;
use tree_sitter::{Node, Parser};
pub fn discover_mutations(path: &Path) -> Result<Vec<MutationPoint>> {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
let Some(lang) = Lang::from_ext(ext) else {
return Ok(Vec::new()); };
let source = fs::read_to_string(path)
.with_context(|| format!("Failed to read {}", path.display()))?;
let mut parser = Parser::new();
parser
.set_language(lang.grammar())
.context("Failed to set parser language")?;
let tree = parser
.parse(&source, None)
.context("Failed to parse file")?;
let mut points = Vec::new();
collect_mutations(tree.root_node(), &source, path, &mut points);
Ok(points)
}
fn collect_mutations(node: Node, source: &str, path: &Path, out: &mut Vec<MutationPoint>) {
if let Some(point) = check_node(node, source, path) {
out.push(point);
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
collect_mutations(child, source, path, out);
}
}
fn check_node(node: Node, source: &str, path: &Path) -> Option<MutationPoint> {
let kind = node.kind();
if !is_mutable_kind(kind) {
return None;
}
let text = node.utf8_text(source.as_bytes()).ok()?;
let (mutated, mutation_kind) = get_mutation(text)?;
Some(MutationPoint {
file: path.to_path_buf(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
byte_start: node.start_byte(),
byte_end: node.end_byte(),
original: text.to_string(),
mutated: mutated.to_string(),
kind: mutation_kind,
})
}
fn is_mutable_kind(kind: &str) -> bool {
matches!(
kind,
"==" | "!=" | "<" | ">" | "<=" | ">="
| "&&" | "||"
| "+" | "-" | "*" | "/"
| "and" | "or"
| "true" | "false"
| "boolean" | "True" | "False"
| "binary_operator"
| "comparison_operator"
| "boolean_operator"
)
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_discover_finds_operators() {
let source = "fn test() { x == 1 && y > 2 }";
let mut parser = Parser::new();
parser.set_language(Lang::Rust.grammar()).ok();
let tree = parser.parse(source, None).expect("parse");
let mut points = Vec::new();
let path = PathBuf::from("test.rs");
collect_mutations(tree.root_node(), source, &path, &mut points);
assert!(!points.is_empty());
}
}