use crate::analysis::parse::ParsedFile;
use std::collections::HashSet;
use tree_sitter::Tree;
mod common;
mod go;
mod jvm;
pub mod lines;
mod python;
mod rust;
mod ts;
pub fn extract_imports(content: &str, language: Option<&str>) -> Vec<String> {
extract_imports_from(&ParsedFile::new(content, language), language)
}
pub(crate) fn extract_imports_from(parsed: &ParsedFile, language: Option<&str>) -> Vec<String> {
let content = parsed.content();
let set: HashSet<String> = match language {
Some("Rust") => from_tree(parsed, |tree| rust::extract(tree, content)),
Some("TypeScript")
| Some("TypeScript React")
| Some("JavaScript")
| Some("JavaScript React") => from_tree(parsed, |tree| ts::extract(tree, content)),
Some("Python") => from_tree(parsed, |tree| python::extract(tree, content)),
Some("Go") => go::extract(content),
Some("Java") => jvm::extract_java(content),
Some("Kotlin") => jvm::extract_kotlin(content),
_ => return Vec::new(),
};
set.into_iter().collect()
}
pub(crate) fn extract_deferred_imports_from(
parsed: &ParsedFile,
language: Option<&str>,
) -> Vec<String> {
let content = parsed.content();
let set: HashSet<String> = match language {
Some("Python") => from_tree(parsed, |tree| python::extract_deferred(tree, content)),
Some("TypeScript")
| Some("TypeScript React")
| Some("JavaScript")
| Some("JavaScript React") => {
from_tree(parsed, |tree| ts::extract_type_only(tree, content))
}
_ => return Vec::new(),
};
set.into_iter().collect()
}
fn from_tree(parsed: &ParsedFile, visit: impl FnOnce(&Tree) -> HashSet<String>) -> HashSet<String> {
parsed.tree().map(visit).unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
fn deferred(content: &str) -> Vec<String> {
let mut d = extract_deferred_imports_from(
&ParsedFile::new(content, Some("Python")),
Some("Python"),
);
d.sort();
d
}
#[test]
fn module_level_python_import_is_not_deferred() {
let content = "from app.models import User\n\ndef f():\n return User\n";
assert!(extract_imports(content, Some("Python")).contains(&"app.models".to_string()));
assert!(deferred(content).is_empty());
}
#[test]
fn function_body_python_import_is_deferred() {
let content = "def handler():\n from app.permissions import policy\n return policy\n";
assert!(extract_imports(content, Some("Python")).contains(&"app.permissions".to_string()));
assert_eq!(
deferred(content),
vec![
"app.permissions".to_string(),
"app.permissions.policy".to_string()
]
);
}
#[test]
fn import_used_both_eagerly_and_lazily_is_not_deferred() {
let content = "from app.models import User\n\ndef f():\n from app.models import User\n return User\n";
assert!(deferred(content).is_empty());
}
#[test]
fn non_python_languages_have_no_deferred_imports() {
let content = "use crate::app::models::User;\nfn f() { use crate::app::other::X; }\n";
assert!(
extract_deferred_imports_from(&ParsedFile::new(content, Some("Rust")), Some("Rust"))
.is_empty()
);
}
fn type_only(content: &str) -> Vec<String> {
let mut d = extract_deferred_imports_from(
&ParsedFile::new(content, Some("TypeScript")),
Some("TypeScript"),
);
d.sort();
d
}
#[test]
fn ts_import_type_is_type_only_but_still_a_full_edge() {
let content = "import type { User } from \"./types\";\nimport { run } from \"./run\";\n";
let imports = extract_imports(content, Some("TypeScript"));
assert!(imports.contains(&"./types".to_string()));
assert!(imports.contains(&"./run".to_string()));
assert_eq!(type_only(content), vec!["./types".to_string()]);
}
#[test]
fn ts_value_and_mixed_imports_are_not_type_only() {
let content = "import { A } from \"./a\";\nimport { type T, b } from \"./b\";\n";
assert!(type_only(content).is_empty(), "{:?}", type_only(content));
}
#[test]
fn ts_all_inline_type_specifiers_are_type_only() {
let content = "import { type A, type B } from \"./types\";\n\
export { type C } from \"./config\";\n";
assert_eq!(
type_only(content),
vec!["./config".to_string(), "./types".to_string()]
);
}
#[test]
fn ts_inline_type_and_value_specifiers_keep_runtime_edge() {
let content = "import { type A, b } from \"./module\";\n";
assert!(type_only(content).is_empty());
}
#[test]
fn ts_export_type_reexport_is_type_only_but_value_barrel_is_not() {
let content = "export type { Cfg } from \"./cfg\";\nexport { thing } from \"./thing\";\n";
assert_eq!(type_only(content), vec!["./cfg".to_string()]);
}
#[test]
fn ts_module_imported_both_type_and_value_is_not_type_only() {
let content = "import type { T } from \"./x\";\nimport { run } from \"./x\";\n";
assert!(type_only(content).is_empty());
}
}