use crate::lang::Lang;
use std::path::Path;
use tree_sitter::{Language, Parser, Query, QueryCursor};
#[must_use]
pub fn extract(path: &Path, content: &str) -> Vec<String> {
let Some(ext) = path.extension().and_then(|s| s.to_str()) else {
return Vec::new();
};
let Some(lang) = Lang::from_ext(ext) else {
return Vec::new();
};
let grammar = lang.grammar();
let query = compile_query(grammar, lang.q_imports());
run_query(content, grammar, &query)
}
fn run_query(source: &str, lang: Language, query: &Query) -> Vec<String> {
let mut parser = Parser::new();
if parser.set_language(lang).is_err() {
return Vec::new();
}
let Some(tree) = parser.parse(source, None) else {
return Vec::new();
};
let mut cursor = QueryCursor::new();
let matches = cursor.matches(query, tree.root_node(), source.as_bytes());
let mut imports = Vec::new();
for m in matches {
for capture in m.captures {
if let Ok(text) = capture.node.utf8_text(source.as_bytes()) {
imports.push(clean_text(text));
}
}
}
imports
}
fn clean_text(text: &str) -> String {
text.trim_matches(|c| c == '"' || c == '\'' || c == '`')
.to_string()
}
fn compile_query(lang: Language, pattern: &str) -> Query {
match Query::new(lang, pattern) {
Ok(q) => q,
Err(e) => panic!("Invalid import query: {e}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_extract_imports() {
let cases = vec![
(
"main.rs",
r"use std::io; use crate::config::Config; mod tests;",
vec!["std::io", "crate::config::Config", "tests"],
),
(
"script.py",
r"import os; from sys import path; import numpy as np",
vec!["os", "sys", "numpy"],
),
(
"app.ts",
r#"import { Foo } from "./components"; const fs = require('fs'); export * from "./utils";"#,
vec!["./components", "fs", "./utils"],
),
(
"lib.rs",
r"pub use std::collections::HashMap; pub use crate::config;",
vec!["std::collections::HashMap", "crate::config"],
),
];
for (filename, code, expected) in cases {
let imports = extract(Path::new(filename), code);
for item in expected {
assert!(
imports.contains(&item.to_string()),
"File {filename} missing import {item}"
);
}
}
}
}