use perl_parser_core::SourceLocation;
pub fn module_already_imported(source: &str, module: &str) -> bool {
for line in source.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("use ") {
let rest = rest.trim_start();
if let Some(after) = rest.strip_prefix(module) {
if after.is_empty()
|| after.starts_with(';')
|| after.starts_with(' ')
|| after.starts_with('\t')
|| after.starts_with('(')
{
return true;
}
}
}
if let Some(rest) = trimmed.strip_prefix("require ")
&& let Some(after) = rest.trim_start().strip_prefix(module)
&& (after.is_empty() || after.starts_with(';') || after.starts_with(' '))
{
return true;
}
}
false
}
pub fn find_use_block_end(source: &str) -> usize {
let mut last_use_line_end: Option<usize> = None;
let mut offset = 0usize;
for line in source.lines() {
let trimmed = line.trim();
let line_byte_len = line.len() + 1;
let is_use_line = trimmed.starts_with("use ")
|| trimmed.starts_with("require ")
|| trimmed == "use strict"
|| trimmed == "use warnings"
|| trimmed == "use utf8"
|| trimmed == "#!/usr/bin/perl"
|| trimmed.starts_with('#')
|| trimmed.is_empty();
if is_use_line {
let is_real_use = trimmed.starts_with("use ") || trimmed.starts_with("require ");
if is_real_use {
last_use_line_end = Some(offset + line_byte_len);
}
}
offset += line_byte_len;
}
last_use_line_end.unwrap_or(0)
}
pub fn build_auto_import_edit(source: &str, module: &str) -> Option<(SourceLocation, String)> {
if module.is_empty() || module_already_imported(source, module) {
return None;
}
let insert_offset = find_use_block_end(source);
let insert_text = format!("use {module};\n");
Some((SourceLocation { start: insert_offset, end: insert_offset }, insert_text))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn already_imported_use_plain() {
let src = "use strict;\nuse DBI;\nmy $x = 1;\n";
assert!(module_already_imported(src, "DBI"));
}
#[test]
fn already_imported_use_qw() {
let src = "use List::Util qw(sum min max);\n";
assert!(module_already_imported(src, "List::Util"));
}
#[test]
fn already_imported_use_parens() {
let src = "use JSON ();\n";
assert!(module_already_imported(src, "JSON"));
}
#[test]
fn not_imported_when_absent() {
let src = "use strict;\nuse warnings;\nmy $x = 1;\n";
assert!(!module_already_imported(src, "DBI"));
}
#[test]
fn not_imported_prefix_match_only() {
let src = "use DBIx::Class;\n";
assert!(!module_already_imported(src, "DBI"));
}
#[test]
fn already_imported_require() {
let src = "require LWP::UserAgent;\n";
assert!(module_already_imported(src, "LWP::UserAgent"));
}
#[test]
fn insert_offset_after_use_block() {
let src = "use strict;\nuse warnings;\n\nsub foo { }\n";
let offset = find_use_block_end(src);
assert_eq!(&src[offset..], "\nsub foo { }\n");
}
#[test]
fn insert_offset_zero_when_no_use() {
let src = "sub foo { }\n";
assert_eq!(find_use_block_end(src), 0);
}
#[test]
fn insert_offset_single_use() {
let src = "use strict;\nsub foo { }\n";
let offset = find_use_block_end(src);
assert_eq!(&src[offset..], "sub foo { }\n");
}
#[test]
fn edit_returned_when_not_imported() {
let src = "use strict;\n\nsub foo { DBI->connect(); }\n";
let edit = build_auto_import_edit(src, "DBI");
assert!(edit.is_some(), "should produce an edit");
let (loc, text) = edit.unwrap();
assert_eq!(text, "use DBI;\n");
assert_eq!(loc.start, 12);
assert_eq!(loc.end, 12, "zero-width insertion");
}
#[test]
fn no_edit_when_already_imported() {
let src = "use strict;\nuse DBI;\n\nsub foo { }\n";
assert!(build_auto_import_edit(src, "DBI").is_none());
}
#[test]
fn no_edit_for_empty_module_name() {
let src = "use strict;\n";
assert!(build_auto_import_edit(src, "").is_none());
}
#[test]
fn edit_inserts_at_top_when_no_use_block() {
let src = "sub foo { LWP::UserAgent->new(); }\n";
let edit = build_auto_import_edit(src, "LWP::UserAgent");
assert!(edit.is_some());
let (loc, text) = edit.unwrap();
assert_eq!(loc.start, 0);
assert_eq!(text, "use LWP::UserAgent;\n");
}
#[test]
fn edit_for_submodule_name() {
let src = "use strict;\n";
let edit = build_auto_import_edit(src, "JSON::MaybeXS");
assert!(edit.is_some());
let (_, text) = edit.unwrap();
assert_eq!(text, "use JSON::MaybeXS;\n");
}
}