use std::ops::Range;
fn language_for_extension(ext: &str) -> Option<tree_sitter::Language> {
match ext {
"py" => Some(tree_sitter_python::LANGUAGE.into()),
"js" | "mjs" | "cjs" | "jsx" | "ts" | "tsx" => {
Some(tree_sitter_javascript::LANGUAGE.into())
}
_ => None,
}
}
fn collect_non_code_ranges(node: tree_sitter::Node, ranges: &mut Vec<Range<usize>>) {
let kind = node.kind();
if kind == "comment" {
ranges.push(node.start_byte()..node.end_byte());
return;
}
if kind == "expression_statement" {
if let Some(child) = node.child(0) {
if child.kind() == "string" {
if let Some(parent) = node.parent() {
let parent_kind = parent.kind();
if parent_kind == "module" || parent_kind == "block" {
ranges.push(node.start_byte()..node.end_byte());
return;
}
}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
collect_non_code_ranges(child, ranges);
}
}
}
pub fn get_non_code_ranges(content: &str, file_extension: &str) -> Option<Vec<Range<usize>>> {
let language = language_for_extension(file_extension)?;
let mut parser = tree_sitter::Parser::new();
parser.set_language(&language).ok()?;
let tree = parser.parse(content, None)?;
let mut ranges = Vec::new();
collect_non_code_ranges(tree.root_node(), &mut ranges);
Some(ranges)
}
#[cfg(test)]
mod tests {
use super::*;
fn is_in_ranges(byte_offset: usize, ranges: &[Range<usize>]) -> bool {
ranges.iter().any(|r| r.contains(&byte_offset))
}
#[test]
fn test_python_comment_detected() {
let code = "# DEBUG = True\nx = 1\n";
let ranges = get_non_code_ranges(code, "py").unwrap();
assert!(is_in_ranges(0, &ranges));
assert!(is_in_ranges(5, &ranges)); }
#[test]
fn test_python_real_code_not_filtered() {
let code = "# comment\nDEBUG = True\n";
let ranges = get_non_code_ranges(code, "py").unwrap();
assert!(!is_in_ranges(10, &ranges));
assert!(!is_in_ranges(15, &ranges));
}
#[test]
fn test_python_docstring_detected() {
let code = "\"\"\"DEBUG = True\"\"\"\nx = 1\n";
let ranges = get_non_code_ranges(code, "py").unwrap();
assert!(is_in_ranges(3, &ranges)); assert!(is_in_ranges(10, &ranges)); }
#[test]
fn test_python_function_docstring_detected() {
let code = "def hello():\n \"\"\"SECRET_KEY = 'test'\"\"\"\n x = 1\n";
let ranges = get_non_code_ranges(code, "py").unwrap();
let docstring_start = code.find("\"\"\"SECRET").unwrap();
assert!(is_in_ranges(docstring_start + 5, &ranges));
}
#[test]
fn test_python_string_literal_not_filtered() {
let code = "x = 'DEBUG = True'\ny = 1\n";
let ranges = get_non_code_ranges(code, "py").unwrap();
let string_start = code.find("'DEBUG").unwrap();
assert!(!is_in_ranges(string_start + 1, &ranges)); }
#[test]
fn test_js_comment_detected() {
let code = "// eval(something)\nconst x = 1;\n";
let ranges = get_non_code_ranges(code, "js").unwrap();
assert!(is_in_ranges(3, &ranges)); }
#[test]
fn test_js_multiline_comment_detected() {
let code = "/* localStorage.setItem('token', t) */\nconst x = 1;\n";
let ranges = get_non_code_ranges(code, "js").unwrap();
assert!(is_in_ranges(5, &ranges)); }
#[test]
fn test_js_string_not_filtered() {
let code = "const msg = \"localStorage.setItem('token', t)\";\n";
let ranges = get_non_code_ranges(code, "js").unwrap();
let string_start = code.find("\"localStorage").unwrap();
assert!(!is_in_ranges(string_start + 1, &ranges)); }
#[test]
fn test_js_real_code_not_filtered() {
let code = "// comment\nlocalStorage.setItem('token', t);\n";
let ranges = get_non_code_ranges(code, "js").unwrap();
let code_start = code.find("localStorage").unwrap();
assert!(!is_in_ranges(code_start, &ranges));
}
#[test]
fn test_unsupported_extension_returns_none() {
let code = "# comment\n";
assert!(get_non_code_ranges(code, "html").is_none());
assert!(get_non_code_ranges(code, "yaml").is_none());
assert!(get_non_code_ranges(code, "md").is_none());
}
#[test]
fn test_unsupported_extension_never_filters() {
assert!(get_non_code_ranges("anything", "html").is_none());
assert!(get_non_code_ranges("anything", "rs").is_none());
}
#[test]
fn test_typescript_uses_js_grammar() {
let code = "// comment\nconst x: number = 1;\n";
let ranges = get_non_code_ranges(code, "ts").unwrap();
assert!(is_in_ranges(3, &ranges)); }
#[test]
fn test_tsx_uses_js_grammar() {
let code = "// comment\nconst App = () => <div/>;\n";
let ranges = get_non_code_ranges(code, "tsx").unwrap();
assert!(is_in_ranges(3, &ranges));
}
}