use std::collections::BTreeSet;
pub fn extract_imports(content: &str, language: Option<&str>) -> Vec<String> {
let set: BTreeSet<String> = match language {
Some("Rust") => extract_rust(content),
Some("TypeScript")
| Some("TypeScript React")
| Some("JavaScript")
| Some("JavaScript React") => extract_ts(content),
Some("Python") => extract_python(content),
Some("Go") => extract_go(content),
_ => return Vec::new(),
};
set.into_iter().collect()
}
fn extract_rust(content: &str) -> BTreeSet<String> {
let mut result = BTreeSet::new();
let mut in_block_comment = false;
let mut pending: Option<String> = None;
for line in content.lines() {
let trimmed = line.trim();
if in_block_comment {
if trimmed.contains("*/") {
in_block_comment = false;
}
continue;
}
if trimmed.starts_with("/*") {
if !trimmed.contains("*/") {
in_block_comment = true;
}
continue;
}
if trimmed.starts_with("//") || trimmed.starts_with('*') {
continue;
}
if let Some(acc) = pending.take() {
let combined = acc + " " + trimmed;
if combined.contains(';') {
for imp in rust_use_imports(&combined) {
result.insert(imp);
}
} else {
pending = Some(combined);
}
continue;
}
let effective = strip_rust_visibility(trimmed);
if let Some(rest) = effective.strip_prefix("use ") {
if rest.contains(';') {
for imp in rust_use_imports(effective) {
result.insert(imp);
}
} else {
pending = Some(effective.to_string());
}
} else if let Some(rest) = effective.strip_prefix("mod ") {
let rest = rest.trim();
if rest.ends_with(';') {
let name = rest.trim_end_matches(';').trim();
if !name.is_empty() && !name.contains('{') && !name.contains(' ') {
result.insert(format!("mod::{name}"));
}
}
}
}
result
}
fn strip_rust_visibility(s: &str) -> &str {
if let Some(rest) = s.strip_prefix("pub(") {
if let Some(close) = rest.find(')') {
return rest[close + 1..].trim_start();
}
}
s.strip_prefix("pub ").unwrap_or(s)
}
fn rust_use_imports(stmt: &str) -> Vec<String> {
let stmt = stmt.trim();
let body = stmt.strip_prefix("use ").unwrap_or(stmt);
let body = body.trim_end_matches(';').trim();
if let Some(brace_pos) = body.find('{') {
let prefix = body[..brace_pos].trim_end_matches(':');
let after = &body[brace_pos + 1..];
let inner = after.trim_end_matches('}').trim();
return inner
.split(',')
.filter_map(|item| {
let item = item.trim();
if item.is_empty() || item == "_" || item == "self" {
return None;
}
let path = item.split(" as ").next().unwrap_or(item).trim();
if path.is_empty() {
return None;
}
if prefix.is_empty() {
Some(path.to_string())
} else {
Some(format!("{prefix}::{path}"))
}
})
.collect();
}
let path = body.split(" as ").next().unwrap_or(body).trim();
if path.is_empty() {
vec![]
} else {
vec![path.to_string()]
}
}
fn extract_ts(content: &str) -> BTreeSet<String> {
let mut result = BTreeSet::new();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with('*') || trimmed.starts_with("/*") {
continue;
}
if (trimmed.starts_with("import ") || trimmed.starts_with("export "))
&& trimmed.contains(" from ")
{
if let Some(path) = extract_from_path(trimmed) {
if is_relative(path) {
result.insert(path.to_string());
}
}
}
if trimmed.contains("require(") {
if let Some(path) = extract_require_path(trimmed) {
if is_relative(path) {
result.insert(path.to_string());
}
}
}
}
result
}
fn extract_from_path(line: &str) -> Option<&str> {
let pos = line.rfind(" from ")?;
let after = line[pos + 6..].trim();
extract_string_literal(after)
}
fn extract_require_path(line: &str) -> Option<&str> {
let pos = line.find("require(")?;
let after = line[pos + 8..].trim();
extract_string_literal(after)
}
fn extract_string_literal(s: &str) -> Option<&str> {
if let Some(rest) = s.strip_prefix('"') {
let end = rest.find('"')?;
Some(&rest[..end])
} else if let Some(rest) = s.strip_prefix('\'') {
let end = rest.find('\'')?;
Some(&rest[..end])
} else if let Some(rest) = s.strip_prefix('`') {
let end = rest.find('`')?;
Some(&rest[..end])
} else {
None
}
}
fn is_relative(path: &str) -> bool {
path.starts_with('.') || path.starts_with('/')
}
fn extract_python(content: &str) -> BTreeSet<String> {
let mut result = BTreeSet::new();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with('#') {
continue;
}
if let Some(rest) = trimmed.strip_prefix("from ") {
if let Some(import_pos) = rest.find(" import ") {
let module = rest[..import_pos].trim();
if !module.is_empty() {
result.insert(module.to_string());
}
}
continue;
}
if let Some(rest) = trimmed.strip_prefix("import ") {
for part in rest.split(',') {
let module = part.split(" as ").next().unwrap_or(part).trim();
if !module.is_empty() {
result.insert(module.to_string());
}
}
}
}
result
}
fn extract_go(content: &str) -> BTreeSet<String> {
let mut result = BTreeSet::new();
let mut in_import_block = false;
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with("//") {
continue;
}
if trimmed == "import (" {
in_import_block = true;
continue;
}
if in_import_block {
if trimmed == ")" {
in_import_block = false;
continue;
}
if let Some(path) = extract_go_import_path(trimmed) {
result.insert(path.to_string());
}
continue;
}
if let Some(rest) = trimmed.strip_prefix("import ") {
if let Some(path) = extract_string_literal(rest.trim()) {
result.insert(path.to_string());
}
}
}
result
}
fn extract_go_import_path(line: &str) -> Option<&str> {
let start = line.find('"')?;
let rest = &line[start + 1..];
let end = rest.find('"')?;
Some(&rest[..end])
}