Files
codex/codex-rs/core/src/mentions.rs
T
Michael Bolin 0c8a36676a fix: move inline codex-rs/core unit tests into sibling files (#14444)
## Why
PR #13783 moved the `codex.rs` unit tests into `codex_tests.rs`. This
applies the same extraction pattern across the rest of `codex-rs/core`
so the production modules stay focused on runtime code instead of large
inline test blocks.

Keeping the tests in sibling files also makes follow-up edits easier to
review because product changes no longer have to share a file with
hundreds or thousands of lines of test scaffolding.

## What changed
- replaced each inline `mod tests { ... }` in `codex-rs/core/src/**`
with a path-based module declaration
- moved each extracted unit test module into a sibling `*_tests.rs`
file, using `mod_tests.rs` for `mod.rs` modules
- preserved the existing `cfg(...)` guards and module-local structure so
the refactor remains structural rather than behavioral

## Testing
- `cargo test -p codex-core --lib` (`1653 passed; 0 failed; 5 ignored`)
- `just fix -p codex-core`
- `cargo fmt --check`
- `cargo shear`
2026-03-12 08:16:36 -07:00

137 lines
4.4 KiB
Rust

use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
use codex_protocol::user_input::UserInput;
use crate::connectors;
use crate::mention_syntax::PLUGIN_TEXT_MENTION_SIGIL;
use crate::mention_syntax::TOOL_MENTION_SIGIL;
use crate::plugins::PluginCapabilitySummary;
use crate::skills::SkillMetadata;
use crate::skills::injection::ToolMentionKind;
use crate::skills::injection::app_id_from_path;
use crate::skills::injection::extract_tool_mentions_with_sigil;
use crate::skills::injection::plugin_config_name_from_path;
use crate::skills::injection::tool_kind_for_path;
pub(crate) struct CollectedToolMentions {
pub(crate) plain_names: HashSet<String>,
pub(crate) paths: HashSet<String>,
}
pub(crate) fn collect_tool_mentions_from_messages(messages: &[String]) -> CollectedToolMentions {
collect_tool_mentions_from_messages_with_sigil(messages, TOOL_MENTION_SIGIL)
}
fn collect_tool_mentions_from_messages_with_sigil(
messages: &[String],
sigil: char,
) -> CollectedToolMentions {
let mut plain_names = HashSet::new();
let mut paths = HashSet::new();
for message in messages {
let mentions = extract_tool_mentions_with_sigil(message, sigil);
plain_names.extend(mentions.plain_names().map(str::to_string));
paths.extend(mentions.paths().map(str::to_string));
}
CollectedToolMentions { plain_names, paths }
}
pub(crate) fn collect_explicit_app_ids(input: &[UserInput]) -> HashSet<String> {
let messages = input
.iter()
.filter_map(|item| match item {
UserInput::Text { text, .. } => Some(text.clone()),
_ => None,
})
.collect::<Vec<String>>();
input
.iter()
.filter_map(|item| match item {
UserInput::Mention { path, .. } => Some(path.clone()),
_ => None,
})
.chain(collect_tool_mentions_from_messages(&messages).paths)
.filter(|path| tool_kind_for_path(path.as_str()) == ToolMentionKind::App)
.filter_map(|path| app_id_from_path(path.as_str()).map(str::to_string))
.collect()
}
/// Collect explicit structured or linked `plugin://...` mentions.
pub(crate) fn collect_explicit_plugin_mentions(
input: &[UserInput],
plugins: &[PluginCapabilitySummary],
) -> Vec<PluginCapabilitySummary> {
if plugins.is_empty() {
return Vec::new();
}
let messages = input
.iter()
.filter_map(|item| match item {
UserInput::Text { text, .. } => Some(text.clone()),
_ => None,
})
.collect::<Vec<String>>();
let mentioned_config_names: HashSet<String> = input
.iter()
.filter_map(|item| match item {
UserInput::Mention { path, .. } => Some(path.clone()),
_ => None,
})
.chain(
// Plugin plaintext links use `@`, not the default `$` tool sigil.
collect_tool_mentions_from_messages_with_sigil(&messages, PLUGIN_TEXT_MENTION_SIGIL)
.paths,
)
.filter(|path| tool_kind_for_path(path.as_str()) == ToolMentionKind::Plugin)
.filter_map(|path| plugin_config_name_from_path(path.as_str()).map(str::to_string))
.collect();
if mentioned_config_names.is_empty() {
return Vec::new();
}
plugins
.iter()
.filter(|plugin| mentioned_config_names.contains(plugin.config_name.as_str()))
.cloned()
.collect()
}
pub(crate) fn build_skill_name_counts(
skills: &[SkillMetadata],
disabled_paths: &HashSet<PathBuf>,
) -> (HashMap<String, usize>, HashMap<String, usize>) {
let mut exact_counts: HashMap<String, usize> = HashMap::new();
let mut lower_counts: HashMap<String, usize> = HashMap::new();
for skill in skills {
if disabled_paths.contains(&skill.path_to_skills_md) {
continue;
}
*exact_counts.entry(skill.name.clone()).or_insert(0) += 1;
*lower_counts
.entry(skill.name.to_ascii_lowercase())
.or_insert(0) += 1;
}
(exact_counts, lower_counts)
}
pub(crate) fn build_connector_slug_counts(
connectors: &[connectors::AppInfo],
) -> HashMap<String, usize> {
let mut counts: HashMap<String, usize> = HashMap::new();
for connector in connectors {
let slug = connectors::connector_mention_slug(connector);
*counts.entry(slug).or_insert(0) += 1;
}
counts
}
#[cfg(test)]
#[path = "mentions_tests.rs"]
mod tests;