use super::Language;
#[derive(Debug)]
pub struct ImportsResult {
pub content: String,
pub added: usize,
pub removed: usize,
pub deduped: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ImportStatement {
pub text: String,
pub line: usize,
}
pub fn list_imports(source: &str, lang: Language) -> Vec<ImportStatement> {
let lines: Vec<&str> = source.lines().collect();
let mut imports = Vec::new();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
if is_top_level_import(line, lang) {
imports.push(ImportStatement {
text: line.trim().to_string(),
line: i + 1,
});
i += 1;
} else if is_multiline_import_start(line, lang) {
let start = i;
let mut block = String::from(line.trim());
i += 1;
while i < lines.len() {
block.push('\n');
block.push_str(lines[i].trim());
if is_multiline_import_end(lines[i], lang) {
break;
}
i += 1;
}
imports.push(ImportStatement {
text: block,
line: start + 1,
});
i += 1;
} else {
i += 1;
}
}
imports
}
pub fn add_imports(source: &str, imports_to_add: &[String], lang: Language) -> ImportsResult {
let eol = crate::write::detect_eol(source);
let lines: Vec<&str> = source.lines().collect();
let existing = list_imports(source, lang);
let existing_texts: std::collections::HashSet<String> =
existing.iter().map(|i| normalize_import(&i.text)).collect();
let mut new_imports = Vec::new();
for imp in imports_to_add {
let normalized = normalize_import(imp);
if !existing_texts.contains(&normalized) {
new_imports.push(format_import(imp, lang));
}
}
if new_imports.is_empty() {
return ImportsResult {
content: source.to_string(),
added: 0,
removed: 0,
deduped: 0,
};
}
let added = new_imports.len();
let insert_after = find_import_insert_point(&lines, lang);
let mut result = String::new();
if insert_after.is_none() && !lines.is_empty() {
for new_imp in &new_imports {
result.push_str(new_imp);
result.push_str(eol);
}
result.push_str(eol);
}
for (i, line) in lines.iter().enumerate() {
result.push_str(line);
result.push_str(eol);
if insert_after == Some(i) {
if existing.is_empty() && !line.is_empty() {
result.push_str(eol);
}
for new_imp in &new_imports {
result.push_str(new_imp);
result.push_str(eol);
}
}
}
if lines.is_empty() {
for new_imp in &new_imports {
result.push_str(new_imp);
result.push_str(eol);
}
}
if !source.ends_with('\n') && result.ends_with('\n') {
result.truncate(result.len() - eol.len());
}
ImportsResult {
content: result,
added,
removed: 0,
deduped: 0,
}
}
pub fn remove_imports(source: &str, imports_to_remove: &[String], lang: Language) -> ImportsResult {
let eol = crate::write::detect_eol(source);
let lines: Vec<&str> = source.lines().collect();
let remove_set: std::collections::HashSet<String> = imports_to_remove
.iter()
.map(|i| normalize_import(i))
.collect();
let remove_names: std::collections::HashSet<&str> = imports_to_remove
.iter()
.map(|i| {
let trimmed = i.trim().trim_end_matches(';').trim();
trimmed
.rsplit("::")
.next()
.or_else(|| trimmed.rsplit(' ').next())
.or_else(|| trimmed.rsplit('.').next())
.unwrap_or(trimmed)
})
.collect();
let mut result = String::new();
let mut removed = 0;
let mut i = 0;
while i < lines.len() {
let line = lines[i];
let trimmed = line.trim();
if is_top_level_import(line, lang) && remove_set.contains(&normalize_import(trimmed)) {
removed += 1;
i += 1;
continue;
}
if is_multiline_import_start(line, lang) {
let block_start = i;
let mut block_lines: Vec<&str> = vec![line];
let mut terminated = false;
i += 1;
while i < lines.len() {
block_lines.push(lines[i]);
if is_multiline_import_end(lines[i], lang) {
terminated = true;
break;
}
i += 1;
}
i += 1;
if !terminated {
for bl in &block_lines {
result.push_str(bl);
result.push_str(eol);
}
continue;
}
let block_text: String = block_lines
.iter()
.map(|l| l.trim())
.collect::<Vec<_>>()
.join("\n");
let block_names = extract_names_from_block(&block_text);
let names_to_remove: Vec<&str> = block_names
.iter()
.filter(|n| remove_names.contains(n.as_str()))
.map(|n| n.as_str())
.collect();
if names_to_remove.is_empty() {
for bl in &block_lines {
result.push_str(bl);
result.push_str(eol);
}
} else if names_to_remove.len() == block_names.len() {
removed += names_to_remove.len();
} else {
let remove_name_set: std::collections::HashSet<&str> =
names_to_remove.iter().copied().collect();
for bl in &block_lines {
let bt = bl.trim();
let line_name = bt.trim_end_matches(',').trim();
let base_name = line_name.split_whitespace().next().unwrap_or("");
let alias = line_name.split_whitespace().nth(2);
if (remove_name_set.contains(base_name)
|| alias.is_some_and(|a| remove_name_set.contains(a)))
&& bt != block_lines[0].trim()
&& !is_multiline_import_end(bl, lang)
{
removed += 1;
continue;
}
result.push_str(bl);
result.push_str(eol);
}
}
let _ = block_start; continue;
}
result.push_str(line);
result.push_str(eol);
i += 1;
}
if !source.ends_with('\n') && result.ends_with('\n') {
result.truncate(result.len() - eol.len());
}
ImportsResult {
content: result,
added: 0,
removed,
deduped: 0,
}
}
pub fn dedupe_imports(source: &str, lang: Language) -> ImportsResult {
let eol = crate::write::detect_eol(source);
let lines: Vec<&str> = source.lines().collect();
let mut seen = std::collections::HashSet::new();
let mut result = String::new();
let mut deduped = 0;
for line in &lines {
let trimmed = line.trim();
if is_top_level_import(line, lang) {
let normalized = normalize_import(trimmed);
if seen.contains(&normalized) {
deduped += 1;
continue;
}
seen.insert(normalized);
}
result.push_str(line);
result.push_str(eol);
}
if !source.ends_with('\n') && result.ends_with('\n') {
result.truncate(result.len() - eol.len());
}
ImportsResult {
content: result,
added: 0,
removed: 0,
deduped,
}
}
fn is_top_level_import(line: &str, lang: Language) -> bool {
let first = line.as_bytes().first().copied().unwrap_or(b' ');
if first == b' ' || first == b'\t' {
return false;
}
is_import_line(line.trim(), lang)
}
fn is_import_line(trimmed: &str, lang: Language) -> bool {
match lang {
Language::Rust => {
(trimmed.starts_with("use ")
|| trimmed.starts_with("pub use ")
|| trimmed.starts_with("pub(crate) use ")
|| trimmed.starts_with("pub(super) use "))
&& trimmed.ends_with(';')
}
Language::Python => {
(trimmed.starts_with("import ") || trimmed.starts_with("from "))
&& !trimmed.ends_with('(')
}
Language::TypeScript | Language::JavaScript => {
trimmed.starts_with("import ") && !trimmed.ends_with('{')
}
Language::Go => {
trimmed.starts_with("import ") && !trimmed.ends_with('(')
}
Language::Java | Language::Kotlin => trimmed.starts_with("import "),
_ => trimmed.starts_with("import ") || trimmed.starts_with("use "),
}
}
fn is_multiline_import_start(line: &str, lang: Language) -> bool {
let first = line.as_bytes().first().copied().unwrap_or(b' ');
if first == b' ' || first == b'\t' {
return false;
}
let trimmed = line.trim();
match lang {
Language::Python => {
trimmed.starts_with("from ") && trimmed.ends_with('(') && !trimmed.contains(')')
}
Language::Rust => {
(trimmed.starts_with("use ")
|| trimmed.starts_with("pub use ")
|| trimmed.starts_with("pub(crate) use ")
|| trimmed.starts_with("pub(super) use "))
&& trimmed.contains('{')
&& !trimmed.ends_with(';')
}
Language::TypeScript | Language::JavaScript => {
trimmed.starts_with("import ") && trimmed.ends_with('{')
}
Language::Go => {
trimmed == "import (" || (trimmed.starts_with("import") && trimmed.ends_with('('))
}
_ => false,
}
}
fn is_multiline_import_end(line: &str, lang: Language) -> bool {
let trimmed = line.trim();
match lang {
Language::Python => trimmed.starts_with(')'),
Language::Rust => trimmed.starts_with("};"),
Language::TypeScript | Language::JavaScript => {
trimmed.starts_with('}') || trimmed.contains("} from")
}
Language::Go => trimmed.starts_with(')'),
_ => false,
}
}
fn normalize_import(import: &str) -> String {
import.trim().trim_end_matches(';').trim().to_string()
}
fn extract_names_from_block(block: &str) -> Vec<String> {
let mut names = Vec::new();
let inner = if let Some(start) = block.find('(') {
let end = block.rfind(')').unwrap_or(block.len());
&block[start + 1..end]
} else if let Some(start) = block.find('{') {
let end = block.rfind('}').unwrap_or(block.len());
&block[start + 1..end]
} else {
return names;
};
for part in inner.split([',', '\n']) {
let name = part.trim();
if name.is_empty() {
continue;
}
let base = name.split_whitespace().next().unwrap_or("");
let base = base.trim_matches('"').trim_matches('\'');
if !base.is_empty() {
names.push(base.to_string());
}
let mut words = name.split_whitespace();
if let (Some(_), Some(kw), Some(alias)) = (words.next(), words.next(), words.next())
&& kw.eq_ignore_ascii_case("as")
{
names.push(alias.to_string());
}
}
names
}
fn format_import(import: &str, lang: Language) -> String {
let trimmed = import.trim();
match lang {
Language::Rust => {
if trimmed.ends_with(';') {
trimmed.to_string()
} else {
format!("{trimmed};")
}
}
_ => trimmed.to_string(),
}
}
fn find_import_insert_point(lines: &[&str], lang: Language) -> Option<usize> {
let mut last_import_line = None;
let mut i = 0;
while i < lines.len() {
if is_top_level_import(lines[i], lang) {
last_import_line = Some(i);
i += 1;
} else if is_multiline_import_start(lines[i], lang) {
i += 1;
while i < lines.len() {
if is_multiline_import_end(lines[i], lang) {
last_import_line = Some(i);
break;
}
i += 1;
}
i += 1;
} else {
i += 1;
}
}
if let Some(idx) = last_import_line {
return Some(idx);
}
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.is_empty()
|| trimmed.starts_with("//")
|| (trimmed.starts_with('#')
&& (trimmed.starts_with("#!/") || trimmed.starts_with("#![" ) || matches!(lang, Language::Python | Language::Ruby | Language::Shell)))
|| trimmed.starts_with("/*")
|| trimmed.starts_with('*')
|| trimmed.starts_with("*/")
|| trimmed.starts_with("\"\"\"")
|| trimmed.starts_with("'''")
{
continue;
}
return if i > 0 { Some(i - 1) } else { None };
}
Some(if lines.is_empty() { 0 } else { lines.len() - 1 })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn list_rust_imports() {
let source = "use std::io;\nuse std::fs;\n\nfn main() {}\n";
let imports = list_imports(source, Language::Rust);
assert_eq!(imports.len(), 2);
assert_eq!(imports[0].text, "use std::io;");
assert_eq!(imports[0].line, 1);
assert_eq!(imports[1].text, "use std::fs;");
}
#[test]
fn add_rust_import() {
let source = "use std::io;\n\nfn main() {}\n";
let result = add_imports(source, &["use std::fs".into()], Language::Rust);
assert_eq!(result.added, 1);
assert!(result.content.contains("use std::fs;"));
assert!(result.content.contains("use std::io;"));
}
#[test]
fn add_import_idempotent() {
let source = "use std::io;\n\nfn main() {}\n";
let result = add_imports(source, &["use std::io;".into()], Language::Rust);
assert_eq!(result.added, 0);
assert_eq!(result.content, source);
}
#[test]
fn remove_rust_import() {
let source = "use std::io;\nuse std::fs;\n\nfn main() {}\n";
let result = remove_imports(source, &["use std::io".into()], Language::Rust);
assert_eq!(result.removed, 1);
assert!(!result.content.contains("use std::io"));
assert!(result.content.contains("use std::fs;"));
}
#[test]
fn dedupe_rust_imports() {
let source = "use std::io;\nuse std::fs;\nuse std::io;\n\nfn main() {}\n";
let result = dedupe_imports(source, Language::Rust);
assert_eq!(result.deduped, 1);
let count = result.content.matches("use std::io;").count();
assert_eq!(count, 1);
}
#[test]
fn add_python_import() {
let source = "import os\n\ndef main():\n pass\n";
let result = add_imports(source, &["import sys".into()], Language::Python);
assert_eq!(result.added, 1);
assert!(result.content.contains("import sys"));
}
#[test]
fn add_typescript_import() {
let source = "import { foo } from 'bar';\n\nconst x = 1;\n";
let result = add_imports(
source,
&["import { baz } from 'qux';".into()],
Language::TypeScript,
);
assert_eq!(result.added, 1);
assert!(result.content.contains("import { baz } from 'qux'"));
}
#[test]
fn add_import_to_empty_file() {
let source = "";
let result = add_imports(source, &["use std::io;".into()], Language::Rust);
assert_eq!(result.added, 1);
assert!(result.content.contains("use std::io;"));
}
#[test]
fn add_import_file_starts_with_code_no_comments() {
let source = "fn main() {}\n";
let result = add_imports(source, &["use std::io".into()], Language::Rust);
assert_eq!(result.added, 1);
let lines: Vec<&str> = result.content.lines().collect();
let import_pos = lines
.iter()
.position(|l| l.contains("use std::io"))
.unwrap();
let code_pos = lines.iter().position(|l| l.contains("fn main")).unwrap();
assert!(
import_pos < code_pos,
"import (line {import_pos}) must appear before code (line {code_pos}): {:?}",
lines,
);
}
#[test]
fn list_python_imports() {
let source = "import os\nfrom pathlib import Path\n\ndef main():\n pass\n";
let imports = list_imports(source, Language::Python);
assert_eq!(imports.len(), 2);
}
#[test]
fn scoped_use_not_treated_as_top_level() {
let source =
"use std::io;\n\nfn main() {\n use std::fmt;\n println!(\"hello\");\n}\n";
let imports = list_imports(source, Language::Rust);
assert_eq!(imports.len(), 1, "scoped use should not be listed");
assert_eq!(imports[0].text, "use std::io;");
}
#[test]
fn add_import_not_inside_function() {
let source =
"use std::io;\n\nfn main() {\n use std::fmt;\n println!(\"hello\");\n}\n";
let result = add_imports(source, &["use std::fs".into()], Language::Rust);
assert_eq!(result.added, 1);
let lines: Vec<&str> = result.content.lines().collect();
let fs_line = lines
.iter()
.position(|l| l.trim() == "use std::fs;")
.expect("use std::fs; should be present");
let fn_line = lines
.iter()
.position(|l| l.trim().starts_with("fn main"))
.expect("fn main should be present");
assert!(
fs_line < fn_line,
"new import at line {} should be before fn main at line {}",
fs_line,
fn_line
);
}
#[test]
fn remove_import_ignores_scoped_use() {
let source =
"use std::fmt;\n\nfn main() {\n use std::fmt;\n println!(\"hello\");\n}\n";
let result = remove_imports(source, &["use std::fmt".into()], Language::Rust);
assert_eq!(result.removed, 1);
assert!(
result.content.contains(" use std::fmt;"),
"scoped use should be preserved: {}",
result.content
);
}
#[test]
fn add_import_preserves_crlf() {
let source = "use std::io;\r\n\r\nfn main() {}\r\n";
let result = add_imports(source, &["use std::fs".into()], Language::Rust);
assert_eq!(result.added, 1);
let bytes = result.content.as_bytes();
for (i, &b) in bytes.iter().enumerate() {
if b == b'\n' {
assert!(
i > 0 && bytes[i - 1] == b'\r',
"bare LF at byte {i} in: {:?}",
result.content
);
}
}
}
#[test]
fn remove_import_preserves_crlf() {
let source = "use std::io;\r\nuse std::fs;\r\n\r\nfn main() {}\r\n";
let result = remove_imports(source, &["use std::io".into()], Language::Rust);
assert_eq!(result.removed, 1);
let bytes = result.content.as_bytes();
for (i, &b) in bytes.iter().enumerate() {
if b == b'\n' {
assert!(
i > 0 && bytes[i - 1] == b'\r',
"bare LF at byte {i} in: {:?}",
result.content
);
}
}
}
#[test]
fn dedupe_import_preserves_crlf() {
let source = "use std::io;\r\nuse std::fs;\r\nuse std::io;\r\n\r\nfn main() {}\r\n";
let result = dedupe_imports(source, Language::Rust);
assert_eq!(result.deduped, 1);
let bytes = result.content.as_bytes();
for (i, &b) in bytes.iter().enumerate() {
if b == b'\n' {
assert!(
i > 0 && bytes[i - 1] == b'\r',
"bare LF at byte {i} in: {:?}",
result.content
);
}
}
}
#[test]
fn list_python_multiline_import() {
let source = "from module import (\n ClassA,\n ClassB,\n ClassC\n)\n\ndef main():\n pass\n";
let imports = list_imports(source, Language::Python);
assert_eq!(imports.len(), 1, "multi-line import should be one entry");
assert!(imports[0].text.contains("ClassA"));
assert!(imports[0].text.contains("ClassC"));
assert_eq!(imports[0].line, 1);
}
#[test]
fn list_rust_multiline_use() {
let source = "use std::collections::{\n HashMap,\n HashSet,\n BTreeMap,\n};\n\nfn main() {}\n";
let imports = list_imports(source, Language::Rust);
assert_eq!(imports.len(), 1, "multi-line use should be one entry");
assert!(imports[0].text.contains("HashMap"));
assert!(imports[0].text.contains("BTreeMap"));
}
#[test]
fn list_typescript_multiline_import() {
let source = "import {\n useState,\n useEffect,\n useCallback\n} from 'react';\n\nconst x = 1;\n";
let imports = list_imports(source, Language::TypeScript);
assert_eq!(imports.len(), 1, "multi-line import should be one entry");
assert!(imports[0].text.contains("useState"));
assert!(imports[0].text.contains("useCallback"));
}
#[test]
fn list_go_import_block() {
let source = "import (\n\t\"fmt\"\n\t\"os\"\n)\n\nfunc main() {}\n";
let imports = list_imports(source, Language::Go);
assert_eq!(imports.len(), 1, "Go import block should be one entry");
assert!(imports[0].text.contains("fmt"));
assert!(imports[0].text.contains("\"os\""));
}
#[test]
fn go_string_literal_not_import() {
let source =
"package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Println(\"hello\")\n}\n";
let imports = list_imports(source, Language::Go);
assert_eq!(imports.len(), 1, "only the real import should be listed");
assert!(imports[0].text.contains("fmt"));
}
#[test]
fn add_import_skips_existing_in_multiline_block() {
let source = "use std::collections::{\n HashMap,\n HashSet,\n};\n\nfn main() {}\n";
let _result = add_imports(
source,
&["use std::collections::HashMap".into()],
Language::Rust,
);
let result2 = add_imports(source, &["use std::io".into()], Language::Rust);
assert_eq!(result2.added, 1);
assert!(result2.content.contains("use std::io;"));
}
#[test]
fn remove_name_from_multiline_python() {
let source = "from module import (\n ClassA,\n ClassB,\n ClassC\n)\n\ndef main():\n pass\n";
let result = remove_imports(source, &["ClassB".into()], Language::Python);
assert_eq!(result.removed, 1);
assert!(!result.content.contains("ClassB"));
assert!(result.content.contains("ClassA"));
assert!(result.content.contains("ClassC"));
}
#[test]
fn remove_entire_multiline_go_block() {
let source = "import (\n\t\"fmt\"\n\t\"os\"\n)\n\nfunc main() {}\n";
let result = remove_imports(source, &["fmt".into(), "os".into()], Language::Go);
assert_eq!(result.removed, 2);
assert!(!result.content.contains("import ("));
}
#[test]
fn dedupe_ignores_scoped_use() {
let source =
"use std::fmt;\n\nfn main() {\n use std::fmt;\n println!(\"hello\");\n}\n";
let result = dedupe_imports(source, Language::Rust);
assert_eq!(result.deduped, 0, "scoped use is not a duplicate");
assert_eq!(result.content, source);
}
#[test]
fn remove_imports_unterminated_block_preserves_code() {
let source =
"use std::collections::{\n HashMap,\nfn main() {\n let x = HashMap::new();\n}\n";
let result = remove_imports(source, &["HashMap".into()], Language::Rust);
assert!(
result.content.contains("fn main()"),
"code after unterminated block must be preserved: {}",
result.content,
);
assert!(
result.content.contains("HashMap"),
"content inside unterminated block must not be filtered: {}",
result.content,
);
}
#[test]
fn add_import_rust_attribute_not_separated() {
let source = "#[cfg(test)]\nmod tests {\n fn test() {}\n}\n";
let result = add_imports(source, &["use std::io".into()], Language::Rust);
assert_eq!(result.added, 1);
let lines: Vec<&str> = result.content.lines().collect();
let import_pos = lines
.iter()
.position(|l| l.contains("use std::io"))
.expect("import should be present");
let attr_pos = lines
.iter()
.position(|l| l.contains("#[cfg(test)]"))
.expect("attribute should be present");
let mod_pos = lines
.iter()
.position(|l| l.contains("mod tests"))
.expect("mod tests should be present");
assert!(
import_pos < attr_pos,
"import (line {import_pos}) must be before attribute (line {attr_pos})",
);
assert!(
attr_pos < mod_pos,
"attribute (line {attr_pos}) must be directly before mod (line {mod_pos})",
);
}
#[test]
fn add_import_rust_inner_attribute_preserved() {
let source = "#![allow(dead_code)]\n\nfn main() {}\n";
let result = add_imports(source, &["use std::io".into()], Language::Rust);
assert_eq!(result.added, 1);
let lines: Vec<&str> = result.content.lines().collect();
let import_pos = lines
.iter()
.position(|l| l.contains("use std::io"))
.expect("import should be present");
let attr_pos = lines
.iter()
.position(|l| l.contains("#![allow(dead_code)]"))
.expect("inner attribute should be present");
assert!(
import_pos > attr_pos,
"import (line {import_pos}) must be after inner attribute (line {attr_pos})",
);
}
#[test]
fn ts_multiline_import_end_type_annotation() {
let source = "import {\n useState,\n Component, // has { props: {} } shape\n useEffect\n} from 'react';\n\nconst x = 1;\n";
let imports = list_imports(source, Language::TypeScript);
assert_eq!(
imports.len(),
1,
"multi-line import should be a single entry, not split by embedded braces: {imports:?}",
);
assert!(imports[0].text.contains("useEffect"));
}
#[test]
fn add_import_after_multiline_block() {
let source = "use std::collections::{\n HashMap,\n HashSet,\n};\n\nfn main() {}\n";
let result = add_imports(source, &["use std::io".into()], Language::Rust);
assert_eq!(result.added, 1);
let lines: Vec<&str> = result.content.lines().collect();
let block_end = lines
.iter()
.position(|l| l.trim() == "};")
.expect("closing }; should be present");
let new_import = lines
.iter()
.position(|l| l.contains("use std::io"))
.expect("new import should be present");
assert!(
new_import > block_end,
"new import (line {new_import}) must be after multi-line block end (line {block_end})",
);
}
#[test]
fn remove_imports_aliased_python() {
let source =
"from module import (\n ClassA as A,\n ClassB,\n)\n\ndef main():\n pass\n";
let result = remove_imports(source, &["A".into()], Language::Python);
assert_eq!(result.removed, 1);
assert!(
!result.content.contains("ClassA"),
"ClassA as A should be removed: {}",
result.content,
);
assert!(
result.content.contains("ClassB"),
"ClassB should be preserved: {}",
result.content,
);
}
#[test]
fn multiline_import_end_with_trailing_comment_go() {
let source = "import (\n\t\"fmt\"\n\t\"os\"\n) // end imports\n\nfunc main() {}\n";
let imports = list_imports(source, Language::Go);
assert_eq!(
imports.len(),
1,
"should find 1 import block, got: {imports:?}"
);
assert!(
!imports[0].text.contains("func"),
"function should not be treated as part of import block: {}",
imports[0].text
);
}
#[test]
fn multiline_import_end_with_trailing_comment_rust() {
let source = "use std::collections::{\n HashMap,\n HashSet,\n}; // collections\n\nfn main() {}\n";
let imports = list_imports(source, Language::Rust);
assert_eq!(imports.len(), 1, "should find 1 import block: {imports:?}");
assert!(
!imports[0].text.contains("fn main"),
"function should not be part of import block: {}",
imports[0].text
);
}
}