use super::*;
fn find_use_insert_position(content: &str) -> Position {
let info = analyze_use_block(content);
if info.existing.is_empty() {
Position {
line: info.fallback_line,
character: 0,
}
} else {
let last_line = info.existing.last().expect("non-empty checked above").0;
Position {
line: last_line + 1,
character: 0,
}
}
}
#[test]
fn conflict_when_short_name_taken_by_different_fqn() {
let mut use_map = HashMap::new();
use_map.insert("Exception".to_string(), "Cassandra\\Exception".to_string());
assert!(use_import_conflicts("App\\Exception", &use_map));
}
#[test]
fn no_conflict_when_same_fqn() {
let mut use_map = HashMap::new();
use_map.insert("Exception".to_string(), "App\\Exception".to_string());
assert!(!use_import_conflicts("App\\Exception", &use_map));
}
#[test]
fn no_conflict_when_different_short_name() {
let mut use_map = HashMap::new();
use_map.insert("Exception".to_string(), "Cassandra\\Exception".to_string());
assert!(!use_import_conflicts("App\\Collection", &use_map));
}
#[test]
fn conflict_is_case_insensitive() {
let mut use_map = HashMap::new();
use_map.insert("exception".to_string(), "Cassandra\\Exception".to_string());
assert!(use_import_conflicts("App\\Exception", &use_map));
}
#[test]
fn no_conflict_when_use_map_empty() {
let use_map = HashMap::new();
assert!(!use_import_conflicts("App\\Exception", &use_map));
}
#[test]
fn conflict_with_global_class_fqn() {
let mut use_map = HashMap::new();
use_map.insert("Exception".to_string(), "Cassandra\\Exception".to_string());
assert!(use_import_conflicts("Exception", &use_map));
}
#[test]
fn no_conflict_same_fqn_case_insensitive() {
let mut use_map = HashMap::new();
use_map.insert("Exception".to_string(), "app\\exception".to_string());
assert!(!use_import_conflicts("App\\Exception", &use_map));
}
#[test]
fn conflict_when_first_segment_matches_alias() {
let mut use_map = HashMap::new();
use_map.insert("pq".to_string(), "Stringable".to_string());
assert!(use_import_conflicts("pq\\Exception", &use_map));
}
#[test]
fn conflict_when_first_segment_matches_alias_case_insensitive() {
let mut use_map = HashMap::new();
use_map.insert("PQ".to_string(), "Stringable".to_string());
assert!(use_import_conflicts("pq\\Exception", &use_map));
}
#[test]
fn no_leading_segment_conflict_for_single_segment_fqn() {
let mut use_map = HashMap::new();
use_map.insert("Stringable".to_string(), "Stringable".to_string());
assert!(!use_import_conflicts("Stringable", &use_map));
}
#[test]
fn leading_segment_conflict_with_deep_namespace() {
let mut use_map = HashMap::new();
use_map.insert("App".to_string(), "Something".to_string());
assert!(use_import_conflicts("App\\Models\\User", &use_map));
}
#[test]
fn no_leading_segment_conflict_when_no_alias_matches() {
let mut use_map = HashMap::new();
use_map.insert("Exception".to_string(), "Cassandra\\Exception".to_string());
assert!(!use_import_conflicts("App\\Collection", &use_map));
}
#[test]
fn sort_key_simple_use() {
assert_eq!(
extract_use_sort_key("use Foo\\Bar;"),
Some("foo\\bar".to_string())
);
}
#[test]
fn sort_key_with_alias() {
assert_eq!(
extract_use_sort_key("use Foo\\Bar as Baz;"),
Some("foo\\bar".to_string())
);
}
#[test]
fn sort_key_grouped_use() {
assert_eq!(
extract_use_sort_key("use Foo\\{Bar, Baz};"),
Some("foo\\".to_string())
);
}
#[test]
fn sort_key_function_use() {
assert_eq!(
extract_use_sort_key("use function Foo\\bar;"),
Some("function foo\\bar".to_string())
);
}
#[test]
fn sort_key_const_use() {
assert_eq!(
extract_use_sort_key("use const Foo\\BAR;"),
Some("const foo\\bar".to_string())
);
}
#[test]
fn sort_key_leading_backslash_stripped() {
assert_eq!(
extract_use_sort_key("use \\Foo\\Bar;"),
Some("foo\\bar".to_string())
);
}
#[test]
fn sort_key_not_a_use_statement() {
assert_eq!(extract_use_sort_key("class Foo {}"), None);
}
#[test]
fn sort_key_closure_use_ignored() {
assert_eq!(extract_use_sort_key("use ($var)"), None);
}
#[test]
fn collects_existing_uses_with_sort_keys() {
let content = "<?php\nnamespace App;\nuse Foo\\Bar;\nuse Baz\\Qux;\n\nclass X {}\n";
let info = analyze_use_block(content);
assert_eq!(info.existing.len(), 2);
assert_eq!(info.existing[0], (2, "foo\\bar".to_string()));
assert_eq!(info.existing[1], (3, "baz\\qux".to_string()));
}
#[test]
fn fallback_after_namespace_when_no_use() {
let content = "<?php\nnamespace App;\n\nclass X {}\n";
let info = analyze_use_block(content);
assert!(info.existing.is_empty());
assert_eq!(info.fallback_line, 2);
}
#[test]
fn fallback_after_php_open_tag_when_no_namespace() {
let content = "<?php\n\nclass X {}\n";
let info = analyze_use_block(content);
assert!(info.existing.is_empty());
assert_eq!(info.fallback_line, 1);
}
#[test]
fn trait_use_inside_class_not_collected() {
let content = "<?php\nnamespace App;\nuse Foo\\Bar;\n\nclass X {\n use SomeTrait;\n}\n";
let info = analyze_use_block(content);
assert_eq!(info.existing.len(), 1);
assert_eq!(info.existing[0], (2, "foo\\bar".to_string()));
}
#[test]
fn insert_alphabetically_before_first() {
let info = UseBlockInfo {
existing: vec![(2, "app\\zoo".to_string())],
fallback_line: 1,
};
assert_eq!(
info.insert_position_for("App\\Alpha"),
Position {
line: 2,
character: 0,
}
);
}
#[test]
fn insert_alphabetically_after_last() {
let info = UseBlockInfo {
existing: vec![(2, "app\\alpha".to_string())],
fallback_line: 1,
};
assert_eq!(
info.insert_position_for("App\\Zoo"),
Position {
line: 3,
character: 0,
}
);
}
#[test]
fn insert_alphabetically_in_the_middle() {
let info = UseBlockInfo {
existing: vec![(2, "app\\alpha".to_string()), (3, "app\\zoo".to_string())],
fallback_line: 1,
};
assert_eq!(
info.insert_position_for("App\\Middle"),
Position {
line: 3,
character: 0,
}
);
}
#[test]
fn insert_uses_fallback_when_no_existing() {
let info = UseBlockInfo {
existing: vec![],
fallback_line: 2,
};
assert_eq!(
info.insert_position_for("App\\Foo"),
Position {
line: 2,
character: 0,
}
);
}
#[test]
fn insert_case_insensitive_comparison() {
let info = UseBlockInfo {
existing: vec![(2, "app\\alpha".to_string()), (3, "app\\zoo".to_string())],
fallback_line: 1,
};
assert_eq!(
info.insert_position_for("APP\\MIDDLE"),
Position {
line: 3,
character: 0,
}
);
}
#[test]
fn insert_among_three_existing() {
let info = UseBlockInfo {
existing: vec![
(2, "a\\a".to_string()),
(3, "c\\c".to_string()),
(4, "e\\e".to_string()),
],
fallback_line: 1,
};
assert_eq!(
info.insert_position_for("D\\D"),
Position {
line: 4,
character: 0,
}
);
}
#[test]
fn compat_insert_after_last_use_statement() {
let content = "<?php\nnamespace App;\nuse Foo\\Bar;\nuse Baz\\Qux;\n\nclass X {}\n";
let pos = find_use_insert_position(content);
assert_eq!(
pos,
Position {
line: 4,
character: 0
}
);
}
#[test]
fn compat_insert_after_namespace_when_no_use() {
let content = "<?php\nnamespace App;\n\nclass X {}\n";
let pos = find_use_insert_position(content);
assert_eq!(
pos,
Position {
line: 2,
character: 0
}
);
}
#[test]
fn compat_insert_after_php_open_tag_when_no_namespace() {
let content = "<?php\n\nclass X {}\n";
let pos = find_use_insert_position(content);
assert_eq!(
pos,
Position {
line: 1,
character: 0
}
);
}
#[test]
fn compat_trait_use_inside_class_not_treated_as_import() {
let content = "<?php\nnamespace App;\nuse Foo\\Bar;\n\nclass X {\n use SomeTrait;\n}\n";
let pos = find_use_insert_position(content);
assert_eq!(
pos,
Position {
line: 3,
character: 0
}
);
}
#[test]
fn build_edit_inserts_at_correct_alpha_position() {
let info = UseBlockInfo {
existing: vec![(2, "app\\alpha".to_string()), (3, "app\\zoo".to_string())],
fallback_line: 1,
};
let edits = build_use_edit("App\\Middle", &info, &Some("App".to_string()))
.expect("should produce edit");
assert_eq!(edits.len(), 1);
assert_eq!(edits[0].new_text, "use App\\Middle;\n");
assert_eq!(
edits[0].range.start,
Position {
line: 3,
character: 0
}
);
}
#[test]
fn build_edit_skips_global_class_without_namespace() {
let info = UseBlockInfo {
existing: vec![],
fallback_line: 1,
};
assert!(build_use_edit("PDO", &info, &None).is_none());
}
#[test]
fn build_edit_includes_global_class_with_namespace() {
let info = UseBlockInfo {
existing: vec![],
fallback_line: 2,
};
let edits =
build_use_edit("PDO", &info, &Some("App".to_string())).expect("should produce edit");
assert_eq!(edits[0].new_text, "use PDO;\n");
assert_eq!(
edits[0].range.start,
Position {
line: 2,
character: 0
}
);
}
#[test]
fn end_to_end_insert_before_existing_alphabetically() {
let content = concat!(
"<?php\n",
"namespace App;\n",
"use Exception;\n",
"use Stringable;\n",
"\n",
"class X {}\n",
);
let info = analyze_use_block(content);
let edits = build_use_edit("Cassandra\\DefaultCluster", &info, &Some("App".to_string()))
.expect("should produce edit");
assert_eq!(edits[0].new_text, "use Cassandra\\DefaultCluster;\n");
assert_eq!(
edits[0].range.start,
Position {
line: 2,
character: 0,
}
);
}
#[test]
fn end_to_end_insert_after_all_existing() {
let content = concat!(
"<?php\n",
"namespace App;\n",
"use App\\Alpha;\n",
"use App\\Beta;\n",
"\n",
"class X {}\n",
);
let info = analyze_use_block(content);
let edits =
build_use_edit("App\\Zeta", &info, &Some("App".to_string())).expect("should produce edit");
assert_eq!(edits[0].new_text, "use App\\Zeta;\n");
assert_eq!(
edits[0].range.start,
Position {
line: 4,
character: 0,
}
);
}
#[test]
fn end_to_end_insert_between_existing() {
let content = concat!(
"<?php\n",
"namespace App;\n",
"use App\\Alpha;\n",
"use App\\Zeta;\n",
"\n",
"class X {}\n",
);
let info = analyze_use_block(content);
let edits = build_use_edit("App\\Middle", &info, &Some("App".to_string()))
.expect("should produce edit");
assert_eq!(edits[0].new_text, "use App\\Middle;\n");
assert_eq!(
edits[0].range.start,
Position {
line: 3,
character: 0,
}
);
}
#[test]
fn build_function_edit_skips_global_function() {
let info = UseBlockInfo {
existing: vec![],
fallback_line: 1,
};
assert!(
build_use_function_edit("array_map", &info).is_none(),
"Global function (no backslash) should not produce an import"
);
}
#[test]
fn build_function_edit_namespaced_no_existing_imports() {
let info = UseBlockInfo {
existing: vec![],
fallback_line: 2,
};
let edits = build_use_function_edit("Illuminate\\Support\\enum_value", &info)
.expect("namespaced function should produce edit");
assert_eq!(
edits[0].new_text,
"use function Illuminate\\Support\\enum_value;\n"
);
assert_eq!(
edits[0].range.start,
Position {
line: 2,
character: 0,
}
);
}
#[test]
fn build_function_edit_sorts_after_class_imports_with_separator() {
let content = concat!(
"<?php\n",
"namespace App;\n",
"use App\\Models\\User;\n",
"use Symfony\\Component\\HttpKernel;\n",
"\n",
);
let info = analyze_use_block(content);
let edits = build_use_function_edit("Illuminate\\Support\\enum_value", &info)
.expect("should produce edit");
assert_eq!(
edits[0].range.start,
Position {
line: 4,
character: 0,
},
"Function import should be placed after all class imports"
);
assert_eq!(
edits[0].new_text, "\nuse function Illuminate\\Support\\enum_value;\n",
"Should prepend blank line to separate from class imports"
);
}
#[test]
fn build_function_edit_no_separator_when_function_group_exists() {
let content = concat!(
"<?php\n",
"namespace App;\n",
"use App\\Models\\User;\n",
"\n",
"use function App\\Helpers\\format_price;\n",
"\n",
);
let info = analyze_use_block(content);
let edits = build_use_function_edit("Illuminate\\Support\\enum_value", &info)
.expect("should produce edit");
assert_eq!(
edits[0].new_text, "use function Illuminate\\Support\\enum_value;\n",
"Should NOT prepend blank line when function group already exists"
);
assert_eq!(
edits[0].range.start,
Position {
line: 5,
character: 0,
}
);
}
#[test]
fn build_function_edit_alphabetical_among_existing_functions() {
let content = concat!(
"<?php\n",
"use App\\Models\\User;\n",
"\n",
"use function App\\Helpers\\format_price;\n",
"use function Symfony\\String\\u;\n",
"\n",
);
let info = analyze_use_block(content);
let edits = build_use_function_edit("Illuminate\\Support\\enum_value", &info)
.expect("should produce edit");
assert_eq!(
edits[0].range.start,
Position {
line: 4,
character: 0,
}
);
assert_eq!(
edits[0].new_text, "use function Illuminate\\Support\\enum_value;\n",
"No separator needed — already in the function group"
);
}
#[test]
fn build_function_edit_deeply_namespaced() {
let info = UseBlockInfo {
existing: vec![],
fallback_line: 3,
};
let edits = build_use_function_edit("Vendor\\Package\\Sub\\Module\\helper_func", &info)
.expect("deeply namespaced function should produce edit");
assert_eq!(
edits[0].new_text,
"use function Vendor\\Package\\Sub\\Module\\helper_func;\n"
);
}