Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 31 additions & 14 deletions crates/emmylua_code_analysis/src/db_index/module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,23 +254,40 @@ impl LuaModuleIndex {
self.file_module_map.get(file_id)
}

/// Find a module by suffix when exact lookup fails.
///
/// Candidates must either exactly equal `module_path` or end with `.{module_path}`.
/// Among matches, prefer the one with the fewest leading path segments before the suffix,
/// then use lexicographic `full_module_name` ordering as a stable tie-break.
fn fuzzy_find_module(&self, module_path: &str, last_name: &str) -> Option<&ModuleInfo> {
let file_ids = self.module_name_to_file_ids.get(last_name)?;
let suffix_with_boundary = format!(".{}", module_path);

// find the first matched module
for file_id in file_ids {
let module_info = self.file_module_map.get(file_id)?;
if module_info.full_module_name == module_path
|| module_info
.full_module_name
.ends_with(&suffix_with_boundary)
{
return Some(module_info);
}
}

None
file_ids
.iter()
.filter_map(|file_id| {
let module_info = self.file_module_map.get(file_id)?;
let full_module_name = module_info.full_module_name.as_str();
let leading_segment_count = if full_module_name == module_path {
Some(0)
} else {
full_module_name
.strip_suffix(&suffix_with_boundary)
.map(|prefix| {
prefix
.split('.')
.filter(|segment| !segment.is_empty())
.count()
})
}?;

Some((leading_segment_count, module_info))
})
.min_by(|(left_count, left_info), (right_count, right_info)| {
left_count
.cmp(right_count)
.then_with(|| left_info.full_module_name.cmp(&right_info.full_module_name))
})
.map(|(_, module_info)| module_info)
}

/// Find a module node by module path.
Expand Down
23 changes: 23 additions & 0 deletions crates/emmylua_code_analysis/src/db_index/module/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,27 @@ mod tests {
let module_info = m.find_module("event").unwrap();
assert_eq!(module_info.full_module_name, "nvim-cmp.lua.cmp.utils.event");
}

#[test]
fn test_require_fuzzy_match_prefers_shortest_prefix_independent_of_insert_order() {
const PLUGIN_ENTRY: &str = "C:/Users/username/Documents/plugin/treesitter-context.lua";
const LUA_ENTRY: &str = "C:/Users/username/Documents/lua/treesitter-context.lua";

// Validate both insertion orders to ensure lookup does not depend on indexing order.
for paths in [[PLUGIN_ENTRY, LUA_ENTRY], [LUA_ENTRY, PLUGIN_ENTRY]] {
let mut m = LuaModuleIndex::new();
m.update_config(Arc::new(Emmyrc::default()));
m.add_workspace_root(
Path::new("C:/Users/username/Documents").into(),
WorkspaceId::MAIN,
);

for (file_id, path) in [FileId { id: 1 }, FileId { id: 2 }].into_iter().zip(paths) {
m.add_module_by_path(file_id, path);
}

let module_info = m.find_module("treesitter-context").unwrap();
assert_eq!(module_info.full_module_name, "lua.treesitter-context");
}
}
}