aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-10 16:30:20 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-10 16:30:20 +0800
commit03b487f0f93ac32835149d4c1a0fb420341134f8 (patch)
tree80e3e901c054581dcffc09da471053d15eb12560
parent1be60c990f27cd9006fa3db8aa5bd623c33840e8 (diff)
feat(pathf): enforce pedantic lints and update API ergonomics
- Add `#[must_use]` to public constructors and getters - Change `init_with_config` to accept `&PathfinderConfig` instead of owned value - Refactor `map_or_else` and `or_insert_with` for clippy compliance - Add `#![deny(clippy::pedantic)]` and `#![deny(clippy::nursery)]` - Document error cases in public API docs - Simplify duplicated dispatcher_clap analysis logic
-rw-r--r--mingling_pathf/src/config.rs3
-rw-r--r--mingling_pathf/src/lib.rs2
-rw-r--r--mingling_pathf/src/module_pathf.rs17
-rw-r--r--mingling_pathf/src/pattern_analyzer.rs35
-rw-r--r--mingling_pathf/src/patterns/dispatcher.rs24
-rw-r--r--mingling_pathf/src/patterns/dispatcher_clap.rs176
-rw-r--r--mingling_pathf/src/patterns/group.rs34
-rw-r--r--mingling_pathf/src/patterns/metadata.rs7
-rw-r--r--mingling_pathf/src/patterns/pack.rs27
-rw-r--r--mingling_pathf/src/type_mapping_builder.rs22
-rw-r--r--mingling_pathf/test/src/lib.rs4
11 files changed, 164 insertions, 187 deletions
diff --git a/mingling_pathf/src/config.rs b/mingling_pathf/src/config.rs
index 1199b2c..758aa34 100644
--- a/mingling_pathf/src/config.rs
+++ b/mingling_pathf/src/config.rs
@@ -17,7 +17,8 @@ pub struct PathfinderConfig {
impl PathfinderConfig {
/// Create a config with `use_dispatch_tree` enabled.
- pub fn with_dispatch_tree() -> Self {
+ #[must_use]
+ pub const fn with_dispatch_tree() -> Self {
Self {
use_dispatch_tree: true,
}
diff --git a/mingling_pathf/src/lib.rs b/mingling_pathf/src/lib.rs
index 69ff3ac..d54cf58 100644
--- a/mingling_pathf/src/lib.rs
+++ b/mingling_pathf/src/lib.rs
@@ -1,6 +1,8 @@
#![allow(clippy::needless_doctest_main)]
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
+#![deny(clippy::pedantic)]
+#![deny(clippy::nursery)]
pub mod config;
pub mod error;
diff --git a/mingling_pathf/src/module_pathf.rs b/mingling_pathf/src/module_pathf.rs
index f0a06d1..ebb7979 100644
--- a/mingling_pathf/src/module_pathf.rs
+++ b/mingling_pathf/src/module_pathf.rs
@@ -25,11 +25,13 @@ pub struct MappingItem {
impl MappingItem {
/// Returns the path of the source file (relative to the crate root, with `./` prefix).
+ #[must_use]
pub fn file_path(&self) -> &Path {
&self.file_path
}
/// Returns the effective module path corresponding to this file (e.g., `"crate::foo::bar"`).
+ #[must_use]
pub fn module_path(&self) -> &str {
&self.module_path
}
@@ -50,6 +52,12 @@ pub struct ModulePathMapping {
/// Analyzes the module structure of a crate and returns the effective module path for each source file.
///
/// `crate_dir` is the crate root directory (i.e., the directory containing `Cargo.toml`).
+///
+/// # Errors
+///
+/// Returns an error if the `src/` directory does not exist, no entry point file is found,
+/// a source file cannot be read or parsed, a child module file cannot be resolved, or
+/// an I/O error occurs while traversing the crate directory.
pub fn analyze(crate_dir: &Path) -> Result<ModulePathMapping, MinglingPathfinderError> {
let src_dir = crate_dir.join("src");
if !src_dir.is_dir() {
@@ -121,11 +129,8 @@ impl Context {
}
fn relative_path(&self, abs: &Path) -> PathBuf {
- if let Ok(rel) = abs.strip_prefix(&self.crate_dir) {
- PathBuf::from("./").join(rel)
- } else {
- abs.to_path_buf()
- }
+ abs.strip_prefix(&self.crate_dir)
+ .map_or_else(|_| abs.to_path_buf(), |rel| PathBuf::from("./").join(rel))
}
}
@@ -276,7 +281,7 @@ fn collect_reexports(tree: &UseTree, results: &mut Vec<String>) {
UseTree::Rename(rename) => {
results.push(rename.ident.to_string());
}
- _ => {}
+ UseTree::Glob(_) => {}
}
}
diff --git a/mingling_pathf/src/pattern_analyzer.rs b/mingling_pathf/src/pattern_analyzer.rs
index 1fc0cbe..44df7ad 100644
--- a/mingling_pathf/src/pattern_analyzer.rs
+++ b/mingling_pathf/src/pattern_analyzer.rs
@@ -17,16 +17,21 @@ use std::path::Path;
use crate::config::PathfinderConfig;
use crate::error::MinglingPathfinderError;
-use crate::patterns::*;
+use crate::patterns::{
+ ChainPattern, CommandPattern, CompletionPattern, DispatcherClapPattern, DispatcherPattern,
+ GroupPattern, GroupedDerivePattern, HelpPattern, MetadataPattern, PackPattern, RendererPattern,
+};
/// Creates a default `PatternAnalyzer` with all built-in patterns pre-registered.
+#[must_use]
pub fn init() -> PatternAnalyzer {
- init_with_config(PathfinderConfig::default())
+ init_with_config(&PathfinderConfig::default())
}
/// Creates a `PatternAnalyzer` with the given config, used by `mingling_core`'s pathf wrapper
/// to inject feature-dependent settings (e.g., `dispatch_tree`).
-pub fn init_with_config(config: PathfinderConfig) -> PatternAnalyzer {
+#[must_use]
+pub fn init_with_config(config: &PathfinderConfig) -> PatternAnalyzer {
let mut analyzer = PatternAnalyzer::new();
analyzer.add_pattern(PackPattern);
analyzer.add_pattern(GroupPattern);
@@ -57,7 +62,8 @@ pub struct AnalyzeItem {
impl AnalyzeItem {
/// Creates a local `AnalyzeItem` (not foreign, will be prefixed with the file's module path).
- pub fn local(module: String, item_name: String) -> Self {
+ #[must_use]
+ pub const fn local(module: String, item_name: String) -> Self {
Self {
module,
item_name,
@@ -67,7 +73,8 @@ impl AnalyzeItem {
}
/// Creates a local module item — generates a `use path::item_name::*;` glob import.
- pub fn local_module(module: String, item_name: String) -> Self {
+ #[must_use]
+ pub const fn local_module(module: String, item_name: String) -> Self {
Self {
module,
item_name,
@@ -77,7 +84,8 @@ impl AnalyzeItem {
}
/// Creates a foreign `AnalyzeItem` (resolved via `use`, the `module` field is the full import path).
- pub fn foreign(module: String, item_name: String) -> Self {
+ #[must_use]
+ pub const fn foreign(module: String, item_name: String) -> Self {
Self {
module,
item_name,
@@ -95,11 +103,13 @@ pub struct AnalyzeResult {
impl AnalyzeResult {
/// Creates an empty `AnalyzeResult` instance
- pub fn new() -> Self {
+ #[must_use]
+ pub const fn new() -> Self {
Self { items: Vec::new() }
}
/// Formats all items into a set of strings in the format `"::{module_path}::{item_name}"`
+ #[must_use]
pub fn into_formatted(self) -> HashSet<String> {
self.items
.into_iter()
@@ -125,7 +135,7 @@ pub trait AnalyzePattern {
/// Quickly determine whether the file content contains an analyzable item
fn contains(&self, content: &str) -> bool;
- /// Analyze the content and return all found AnalyzeItems
+ /// Analyze the content and return all found `AnalyzeItem`s
fn analyze(&self, content: &str) -> Vec<AnalyzeItem>;
}
@@ -138,6 +148,7 @@ pub struct PatternAnalyzer {
impl PatternAnalyzer {
/// Creates a new empty `PatternAnalyzer`.
+ #[must_use]
pub fn new() -> Self {
Self::default()
}
@@ -148,6 +159,10 @@ impl PatternAnalyzer {
}
/// Analyzes a single file and returns a formatted set of strings.
+ ///
+ /// # Errors
+ ///
+ /// Returns a [`MinglingPathfinderError`] if the file cannot be read.
pub fn analyze_file(
&self,
path: impl AsRef<Path>,
@@ -157,6 +172,10 @@ impl PatternAnalyzer {
}
/// Analyzes a single file and returns the raw `Vec<AnalyzeItem>`.
+ ///
+ /// # Errors
+ ///
+ /// Returns a [`MinglingPathfinderError`] if the file cannot be read.
pub fn analyze_file_items(
&self,
path: impl AsRef<Path>,
diff --git a/mingling_pathf/src/patterns/dispatcher.rs b/mingling_pathf/src/patterns/dispatcher.rs
index cedad9f..85220b5 100644
--- a/mingling_pathf/src/patterns/dispatcher.rs
+++ b/mingling_pathf/src/patterns/dispatcher.rs
@@ -32,7 +32,8 @@ impl DispatcherPattern {
/// * `use_dispatch_tree` — when `true`, the generated dispatcher also produces a
/// `__internal_dispatcher_*` static dispatch tree item. Set this based on whether
/// your macro invocation includes the `use_dispatch_tree` configuration.
- pub fn new(use_dispatch_tree: bool) -> Self {
+ #[must_use]
+ pub const fn new(use_dispatch_tree: bool) -> Self {
Self { use_dispatch_tree }
}
}
@@ -102,9 +103,8 @@ fn extract_all_types(
use_dispatch_tree: bool,
) -> Vec<AnalyzeItem> {
let (cmd_name, cmd_struct, entry_struct) = parse_dispatcher_args(tokens);
- let cmd_name = match cmd_name {
- Some(n) => n,
- None => return Vec::new(),
+ let Some(cmd_name) = cmd_name else {
+ return Vec::new();
};
let mut items = Vec::new();
@@ -128,7 +128,7 @@ fn extract_all_types(
items
}
-/// Parses dispatcher arguments and returns (command_name, cmd_struct, entry_struct).
+/// Parses dispatcher arguments and returns (`command_name`, `cmd_struct`, `entry_struct`).
fn parse_dispatcher_args(
tokens: &proc_macro2::TokenStream,
) -> (Option<String>, Option<String>, Option<String>) {
@@ -166,9 +166,8 @@ fn parse_dispatcher_args(
}
// Implicit form: "name"
- let cmd_name = match extract_string_literal(&stream) {
- Some(n) => n,
- None => return (None, None, None),
+ let Some(cmd_name) = extract_string_literal(&stream) else {
+ return (None, None, None);
};
let pascal = to_pascal_case(&cmd_name);
(
@@ -192,15 +191,14 @@ fn to_pascal_case(s: &str) -> String {
.filter(|s| !s.is_empty())
.map(|s| {
let mut c = s.chars();
- match c.next() {
- None => String::new(),
- Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
- }
+ c.next().map_or_else(String::new, |f| {
+ f.to_uppercase().collect::<String>() + c.as_str()
+ })
})
.collect()
}
-/// Simple snake_case conversion (replaces `.`, `-` with `_`).
+/// Simple `snake_case` conversion (replaces `.`, `-` with `_`).
fn snake_case(s: &str) -> String {
s.replace(['.', '-'], "_").to_lowercase()
}
diff --git a/mingling_pathf/src/patterns/dispatcher_clap.rs b/mingling_pathf/src/patterns/dispatcher_clap.rs
index 25a7093..ca2ce9e 100644
--- a/mingling_pathf/src/patterns/dispatcher_clap.rs
+++ b/mingling_pathf/src/patterns/dispatcher_clap.rs
@@ -39,7 +39,8 @@ impl DispatcherClapPattern {
/// # Parameters
/// - `use_dispatch_tree`: When `true`, enables analysis of the `__internal_dispatcher_*`
/// static dispatch tree for each matched command.
- pub fn new(use_dispatch_tree: bool) -> Self {
+ #[must_use]
+ pub const fn new(use_dispatch_tree: bool) -> Self {
Self { use_dispatch_tree }
}
}
@@ -59,52 +60,7 @@ impl AnalyzePattern for DispatcherClapPattern {
for item in &syntax.items {
match item {
Item::Struct(s) if has_attr(&s.attrs, "dispatcher_clap") => {
- // Entry type (struct name) — always
- let entry_name = s.ident.to_string();
- items.push(AnalyzeItem::local(String::new(), entry_name.clone()));
-
- // Parse the attribute to extract CMD, error, and help info
- if let Some(attr) = s.attrs.iter().find(|a| {
- a.path()
- .segments
- .last()
- .is_some_and(|seg| seg.ident == "dispatcher_clap")
- }) {
- let args = attr.meta.require_list().ok();
- let args_str = args.map(|l| l.tokens.to_string()).unwrap_or_default();
- let parsed = parse_dispatcher_clap_args(&args_str);
-
- // CMD type — always
- if let Some(ref cmd) = parsed.cmd_type {
- items.push(AnalyzeItem::local(String::new(), cmd.clone()));
- }
-
- // Error type — if error = TypeName
- if let Some(ref err) = parsed.error_type {
- items.push(AnalyzeItem::local(String::new(), err.clone()));
- }
-
- // Help internal struct — if help = true
- if parsed.help_enabled
- && let Some(ref cmd) = parsed.cmd_type
- {
- let help_fn = format!("__{}_help", just_fmt::snake_case!(cmd));
- let help_struct =
- format!("__internal_help_{}", just_fmt::snake_case!(&help_fn));
- items.push(AnalyzeItem::local(String::new(), help_struct));
- }
-
- // __internal_dispatcher_* — when configured
- if self.use_dispatch_tree
- && let Some(ref cmd_name) = parsed.cmd_name
- {
- let internal_name = format!(
- "__internal_dispatcher_{}",
- just_fmt::snake_case!(cmd_name)
- );
- items.push(AnalyzeItem::local(String::new(), internal_name));
- }
- }
+ items.extend(self.analyze_struct(s, ""));
}
Item::Mod(item_mod) => {
if let Some((_, nested)) = &item_mod.content {
@@ -112,67 +68,7 @@ impl AnalyzePattern for DispatcherClapPattern {
if let Item::Struct(s) = n
&& has_attr(&s.attrs, "dispatcher_clap")
{
- let entry_name = s.ident.to_string();
- items.push(AnalyzeItem::local(
- item_mod.ident.to_string(),
- entry_name.clone(),
- ));
-
- if let Some(attr) = s.attrs.iter().find(|a| {
- a.path()
- .segments
- .last()
- .is_some_and(|seg| seg.ident == "dispatcher_clap")
- }) {
- let args = attr.meta.require_list().ok();
- let args_str =
- args.map(|l| l.tokens.to_string()).unwrap_or_default();
- let parsed = parse_dispatcher_clap_args(&args_str);
-
- if let Some(ref cmd) = parsed.cmd_type {
- items.push(AnalyzeItem::local(
- item_mod.ident.to_string(),
- cmd.clone(),
- ));
- }
-
- if let Some(ref err) = parsed.error_type {
- items.push(AnalyzeItem::local(
- item_mod.ident.to_string(),
- err.clone(),
- ));
- }
-
- // Help internal struct — same naming rule as root level
- if parsed.help_enabled
- && let Some(ref cmd) = parsed.cmd_type
- {
- let help_fn =
- format!("__{}_help", just_fmt::snake_case!(cmd));
- let help_struct = format!(
- "__internal_help_{}",
- just_fmt::snake_case!(&help_fn)
- );
- items.push(AnalyzeItem::local(
- item_mod.ident.to_string(),
- help_struct,
- ));
- }
-
- // __internal_dispatcher_* — when configured
- if self.use_dispatch_tree
- && let Some(ref cmd_name) = parsed.cmd_name
- {
- let internal_name = format!(
- "__internal_dispatcher_{}",
- just_fmt::snake_case!(cmd_name)
- );
- items.push(AnalyzeItem::local(
- item_mod.ident.to_string(),
- internal_name,
- ));
- }
- }
+ items.extend(self.analyze_struct(s, &item_mod.ident.to_string()));
}
}
}
@@ -185,6 +81,58 @@ impl AnalyzePattern for DispatcherClapPattern {
}
}
+impl DispatcherClapPattern {
+ fn analyze_struct(&self, s: &syn::ItemStruct, module: &str) -> Vec<AnalyzeItem> {
+ let mut items = Vec::new();
+
+ // Entry type (struct name) — always
+ let entry_name = s.ident.to_string();
+ items.push(AnalyzeItem::local(module.to_string(), entry_name));
+
+ // Parse the attribute to extract CMD, error, and help info
+ if let Some(attr) = s.attrs.iter().find(|a| {
+ a.path()
+ .segments
+ .last()
+ .is_some_and(|seg| seg.ident == "dispatcher_clap")
+ }) {
+ let args = attr.meta.require_list().ok();
+ let args_str = args.map(|l| l.tokens.to_string()).unwrap_or_default();
+ let parsed = parse_dispatcher_clap_args(&args_str);
+
+ // CMD type — always
+ if let Some(ref cmd) = parsed.cmd_type {
+ items.push(AnalyzeItem::local(module.to_string(), cmd.clone()));
+ }
+
+ // Error type — if error = TypeName
+ if let Some(ref err) = parsed.error_type {
+ items.push(AnalyzeItem::local(module.to_string(), err.clone()));
+ }
+
+ // Help internal struct — if help = true
+ if parsed.help_enabled
+ && let Some(ref cmd) = parsed.cmd_type
+ {
+ let help_fn = format!("__{}_help", just_fmt::snake_case!(cmd));
+ let help_struct = format!("__internal_help_{}", just_fmt::snake_case!(&help_fn));
+ items.push(AnalyzeItem::local(module.to_string(), help_struct));
+ }
+
+ // __internal_dispatcher_* — when configured
+ if self.use_dispatch_tree
+ && let Some(ref cmd_name) = parsed.cmd_name
+ {
+ let internal_name =
+ format!("__internal_dispatcher_{}", just_fmt::snake_case!(cmd_name));
+ items.push(AnalyzeItem::local(module.to_string(), internal_name));
+ }
+ }
+
+ items
+ }
+}
+
struct ParsedClapArgs {
cmd_name: Option<String>,
cmd_type: Option<String>,
@@ -202,17 +150,13 @@ fn parse_dispatcher_clap_args(args: &str) -> ParsedClapArgs {
let args = args.trim();
// Extract the first quoted string (the command name)
- let after_cmd = if let Some(start) = args.find('"') {
+ let after_cmd = args.find('"').map_or(args, |start| {
let after_open = &args[start + 1..];
- if let Some(end) = after_open.find('"') {
+ after_open.find('"').map_or(args, |end| {
cmd_name = Some(after_open[..end].to_string());
after_open[end + 1..].trim()
- } else {
- args
- }
- } else {
- args
- };
+ })
+ });
// Split by commas and parse each part
for part in after_cmd.split(',') {
diff --git a/mingling_pathf/src/patterns/group.rs b/mingling_pathf/src/patterns/group.rs
index 0f9ecff..905afa9 100644
--- a/mingling_pathf/src/patterns/group.rs
+++ b/mingling_pathf/src/patterns/group.rs
@@ -129,7 +129,8 @@ fn collect_from_use_tree(
UseTree::Name(name) => {
let module = prefix.to_string();
let alias = name.ident.to_string();
- map.entry(alias).or_insert((module, name.ident.to_string()));
+ map.entry(alias)
+ .or_insert_with(|| (module, name.ident.to_string()));
}
UseTree::Path(use_path) => {
let new_prefix = if prefix.is_empty() {
@@ -143,7 +144,7 @@ fn collect_from_use_tree(
let module = prefix.to_string();
let alias = rename.ident.to_string();
map.entry(alias)
- .or_insert((module, rename.ident.to_string()));
+ .or_insert_with(|| (module, rename.ident.to_string()));
}
UseTree::Glob(_) => {
// `use path::*;` — skip glob imports
@@ -178,24 +179,21 @@ fn extract_group_name(tokens: &proc_macro2::TokenStream) -> Option<String> {
let mut iter = stream.into_iter();
loop {
- match iter.next()? {
- proc_macro2::TokenTree::Ident(ident) => {
- let name = ident.to_string();
-
- // Check if there is a `=` following
- let next = iter.next();
- match next {
- Some(proc_macro2::TokenTree::Punct(p)) if p.as_char() == '=' => {
- // group!(Alias = path::Type)
- return Some(name);
- }
- _ => {
- // group!(TypeName)
- return Some(name);
- }
+ if let proc_macro2::TokenTree::Ident(ident) = iter.next()? {
+ let name = ident.to_string();
+
+ // Check if there is a `=` following
+ let next = iter.next();
+ match next {
+ Some(proc_macro2::TokenTree::Punct(p)) if p.as_char() == '=' => {
+ // group!(Alias = path::Type)
+ return Some(name);
+ }
+ _ => {
+ // group!(TypeName)
+ return Some(name);
}
}
- _ => continue,
}
}
}
diff --git a/mingling_pathf/src/patterns/metadata.rs b/mingling_pathf/src/patterns/metadata.rs
index 24243bf..32a283a 100644
--- a/mingling_pathf/src/patterns/metadata.rs
+++ b/mingling_pathf/src/patterns/metadata.rs
@@ -95,7 +95,7 @@ fn extract_bind_type(attrs: &[syn::Attribute]) -> Option<String> {
continue;
}
if let syn::Meta::List(meta_list) = &attr.meta {
- for token in meta_list.tokens.clone().into_iter() {
+ for token in meta_list.tokens.clone() {
if let proc_macro2::TokenTree::Ident(ident) = token {
return Some(ident.to_string());
}
@@ -139,7 +139,8 @@ fn collect_from_use_tree(
UseTree::Name(name) => {
let module = prefix.to_string();
let alias = name.ident.to_string();
- map.entry(alias).or_insert((module, name.ident.to_string()));
+ map.entry(alias)
+ .or_insert_with(|| (module, name.ident.to_string()));
}
UseTree::Path(use_path) => {
let new_prefix = if prefix.is_empty() {
@@ -153,7 +154,7 @@ fn collect_from_use_tree(
let module = prefix.to_string();
let alias = rename.ident.to_string();
map.entry(alias)
- .or_insert((module, rename.ident.to_string()));
+ .or_insert_with(|| (module, rename.ident.to_string()));
}
UseTree::Glob(_) => {}
UseTree::Group(group) => {
diff --git a/mingling_pathf/src/patterns/pack.rs b/mingling_pathf/src/patterns/pack.rs
index 76597ba..e5e14e6 100644
--- a/mingling_pathf/src/patterns/pack.rs
+++ b/mingling_pathf/src/patterns/pack.rs
@@ -84,23 +84,20 @@ fn try_extract_pack_name(m: &syn::ItemMacro) -> Option<String> {
// Skip leading attributes/doc comments
loop {
- match iter.next()? {
- proc_macro2::TokenTree::Ident(ident) => {
- // Found the first ident, this is the type name
- let type_name = ident.to_string();
-
- // Check if `=` follows
- if let Some(proc_macro2::TokenTree::Punct(p)) = iter.next()
- && p.as_char() == '='
- {
- // pack!(TypeName = InnerType)
- return Some(type_name);
- }
-
- // pack_err!(TypeName) — only a single ident
+ if let proc_macro2::TokenTree::Ident(ident) = iter.next()? {
+ // Found the first ident, this is the type name
+ let type_name = ident.to_string();
+
+ // Check if `=` follows
+ if let Some(proc_macro2::TokenTree::Punct(p)) = iter.next()
+ && p.as_char() == '='
+ {
+ // pack!(TypeName = InnerType)
return Some(type_name);
}
- _ => continue,
+
+ // pack_err!(TypeName) — only a single ident
+ return Some(type_name);
}
}
}
diff --git a/mingling_pathf/src/type_mapping_builder.rs b/mingling_pathf/src/type_mapping_builder.rs
index fb59b43..9f8980d 100644
--- a/mingling_pathf/src/type_mapping_builder.rs
+++ b/mingling_pathf/src/type_mapping_builder.rs
@@ -3,6 +3,7 @@
//! type mapping files used by the pathfinder system.
use std::collections::HashSet;
+use std::fmt::Write as FmtWrite;
use std::path::Path;
use crate::config::PathfinderConfig;
@@ -14,16 +15,21 @@ use crate::pattern_analyzer;
///
/// `crate_dir` — crate root directory (i.e., the directory containing Cargo.toml)
/// `output_dir` — directory where mapping files will be written
-/// `config` — pathfinder configuration (e.g., dispatch_tree detection)
+/// `config` — pathfinder configuration (e.g., [`dispatch_tree`] detection)
///
/// Mapping file format per line: `TypeName = crate::module::path::TypeName`
+///
+/// # Errors
+///
+/// Returns a [`MinglingPathfinderError`] if the module analysis fails, the output
+/// directory cannot be created, or the mapping files cannot be written.
pub fn analyze_and_build_type_mapping_for(
crate_dir: &Path,
output_dir: &Path,
config: &PathfinderConfig,
) -> Result<(), MinglingPathfinderError> {
let module_mapping = module_pathf::analyze(crate_dir)?;
- let analyzer = pattern_analyzer::init_with_config(config.clone());
+ let analyzer = pattern_analyzer::init_with_config(config);
let mut type_mappings: Vec<(String, String, bool)> = Vec::new();
@@ -66,16 +72,16 @@ pub fn analyze_and_build_type_mapping_for(
let mut content_mapping = String::new();
for (name, path, _) in &type_mappings {
- content_mapping.push_str(&format!("{name} = {path}\n"));
+ let _ = writeln!(content_mapping, "{name} = {path}");
}
std::fs::write(&output_path, content_mapping)?;
let mut content_using = String::new();
for (_, path, is_module) in &type_mappings {
if *is_module {
- content_using.push_str(&format!("use {path}::*;\n"));
+ let _ = writeln!(content_using, "use {path}::*;");
} else {
- content_using.push_str(&format!("use {path};\n"));
+ let _ = writeln!(content_using, "use {path};");
}
}
std::fs::write(&type_using_path, content_using)?;
@@ -87,6 +93,12 @@ pub fn analyze_and_build_type_mapping_for(
/// from environment variables.
///
/// Reads `CARGO_PKG_NAME` and `OUT_DIR`, and outputs to `{OUT_DIR}/{CARGO_PKG_NAME}/`.
+///
+/// # Errors
+///
+/// Returns a [`MinglingPathfinderError`] if the required environment variables
+/// (`CARGO_PKG_NAME`, `OUT_DIR`) are not set, the current directory cannot be
+/// determined, or the type mapping generation fails.
pub fn analyze_and_build_type_mapping() -> Result<(), MinglingPathfinderError> {
let crate_name = std::env::var("CARGO_PKG_NAME").map_err(|_| {
MinglingPathfinderError::IoError(std::io::Error::new(
diff --git a/mingling_pathf/test/src/lib.rs b/mingling_pathf/test/src/lib.rs
index b543a15..95d7410 100644
--- a/mingling_pathf/test/src/lib.rs
+++ b/mingling_pathf/test/src/lib.rs
@@ -300,7 +300,7 @@ fn test_dispatcher_dispatch_tree() {
assert!(r1.contains("::sub::CMDGreet"));
// With dispatch_tree: Entry + CMD + __internal_dispatcher
- let r2 = pattern_analyzer::init_with_config(PathfinderConfig::with_dispatch_tree())
+ let r2 = pattern_analyzer::init_with_config(&PathfinderConfig::with_dispatch_tree())
.analyze_file(&file)
.unwrap();
// 8 (from above) + 2 __internal (root) + 2 __internal (sub) = 12
@@ -377,7 +377,7 @@ fn test_dispatcher_clap_dispatch_tree() {
assert_eq!(r1.len(), 26);
// With dispatch_tree: 26 + 4 __internal (root) + 3 __internal (sub, no "full") = 33
- let r2 = pattern_analyzer::init_with_config(PathfinderConfig::with_dispatch_tree())
+ let r2 = pattern_analyzer::init_with_config(&PathfinderConfig::with_dispatch_tree())
.analyze_file(&file)
.unwrap();
assert_eq!(r2.len(), 33);