From e65ae91964f59e480429eb91424a6b55018716b0 Mon Sep 17 00:00:00 2001 From: Madinah <497350746@qq.com> Date: Fri, 22 Nov 2024 21:49:06 +0800 Subject: [PATCH] =?UTF-8?q?refactor(rust-plugins):=20=F0=9F=92=A1=20virtua?= =?UTF-8?q?l=20plugin=20(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 123 ++++++++++++-------- rust-plugins/virtual/CHANGELOG.md | 6 + rust-plugins/virtual/Cargo.toml | 1 + rust-plugins/virtual/package.json | 2 +- rust-plugins/virtual/src/lib.rs | 185 ++++++++++++++++++++---------- rust-plugins/virtual/src/utils.rs | 126 ++++++++++++++------ 6 files changed, 300 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8670eed..f11b554 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,7 +155,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -178,7 +178,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -298,7 +298,7 @@ dependencies = [ "once_cell", "serde", "serde_json", - "thiserror", + "thiserror 1.0.63", ] [[package]] @@ -388,7 +388,7 @@ dependencies = [ "rmp-serde", "serde", "sled", - "thiserror", + "thiserror 1.0.63", "web-time", ] @@ -401,7 +401,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -439,7 +439,7 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "thiserror", + "thiserror 1.0.63", ] [[package]] @@ -500,7 +500,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -674,7 +674,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -685,7 +685,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -806,7 +806,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -877,7 +877,7 @@ dependencies = [ "swc_ecma_ast 0.115.1", "swc_ecma_parser 0.146.12", "swc_html_ast 0.34.0", - "thiserror", + "thiserror 1.0.63", "wax", ] @@ -1211,6 +1211,7 @@ dependencies = [ "farmfe_core", "farmfe_macro_plugin", "farmfe_toolkit_plugin_types", + "thiserror 2.0.3", ] [[package]] @@ -1278,7 +1279,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -1474,7 +1475,7 @@ checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4" dependencies = [ "proc-macro2", "swc_macros_common", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -1565,7 +1566,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -1576,7 +1577,7 @@ checksum = "b0e085ded9f1267c32176b40921b9754c474f7dd96f7e808d4a982e48aa1e854" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -1895,7 +1896,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -2116,7 +2117,7 @@ checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" dependencies = [ "miette-derive 5.10.0", "once_cell", - "thiserror", + "thiserror 1.0.63", "unicode-width", ] @@ -2130,7 +2131,7 @@ dependencies = [ "miette-derive 7.2.0", "owo-colors", "textwrap", - "thiserror", + "thiserror 1.0.63", "unicode-width", ] @@ -2142,7 +2143,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -2153,7 +2154,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -2458,7 +2459,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -2599,7 +2600,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -2610,9 +2611,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" dependencies = [ "unicode-ident", ] @@ -2659,7 +2660,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls", "socket2", - "thiserror", + "thiserror 1.0.63", "tokio", "tracing", ] @@ -2676,7 +2677,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls", "slab", - "thiserror", + "thiserror 1.0.63", "tinyvec", "tracing", ] @@ -2791,7 +2792,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.63", ] [[package]] @@ -3154,7 +3155,7 @@ checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -3408,7 +3409,7 @@ checksum = "710e9696ef338691287aeb937ee6ffe60022f579d3c8d2fd9d58973a9a10a466" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -3452,7 +3453,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -3662,7 +3663,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -3781,7 +3782,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -4033,7 +4034,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -4536,7 +4537,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -4801,7 +4802,7 @@ checksum = "63db0adcff29d220c3d151c5b25c0eabe7e32dd936212b84cdaa1392e3130497" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -4904,7 +4905,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -5007,7 +5008,7 @@ checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -5027,7 +5028,7 @@ checksum = "ff9719b6085dd2824fd61938a881937be14b08f95e2d27c64c825a9f65e052ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -5050,7 +5051,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -5101,7 +5102,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -5141,9 +5142,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.85" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -5192,7 +5193,16 @@ version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.63", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", ] [[package]] @@ -5203,7 +5213,18 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", ] [[package]] @@ -5288,7 +5309,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -5327,7 +5348,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] @@ -5479,7 +5500,7 @@ dependencies = [ "enum-iterator", "getset", "rustversion", - "thiserror", + "thiserror 1.0.63", "time", ] @@ -5548,7 +5569,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -5582,7 +5603,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5606,7 +5627,7 @@ dependencies = [ "pori", "regex", "tardar", - "thiserror", + "thiserror 1.0.63", "walkdir", ] @@ -5899,7 +5920,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.89", ] [[package]] diff --git a/rust-plugins/virtual/CHANGELOG.md b/rust-plugins/virtual/CHANGELOG.md index 00e7165..0f48d66 100644 --- a/rust-plugins/virtual/CHANGELOG.md +++ b/rust-plugins/virtual/CHANGELOG.md @@ -1,5 +1,11 @@ # @farmfe/plugin-virtual +## 0.0.8 + +### Patch Changes + +- 3ad90e0: refactor virtual plugin + ## 0.0.7 ### Patch Changes diff --git a/rust-plugins/virtual/Cargo.toml b/rust-plugins/virtual/Cargo.toml index 6a2baf7..c69b50b 100644 --- a/rust-plugins/virtual/Cargo.toml +++ b/rust-plugins/virtual/Cargo.toml @@ -10,3 +10,4 @@ crate-type = ["cdylib", "rlib"] farmfe_toolkit_plugin_types = { workspace = true } farmfe_macro_plugin = { workspace = true } farmfe_core = { workspace = true } +thiserror = "2.0.3" diff --git a/rust-plugins/virtual/package.json b/rust-plugins/virtual/package.json index 27e46c0..8a8d216 100644 --- a/rust-plugins/virtual/package.json +++ b/rust-plugins/virtual/package.json @@ -1,6 +1,6 @@ { "name": "@farmfe/plugin-virtual", - "version": "0.0.7", + "version": "0.0.8", "private": false, "main": "scripts/index.js", "types": "scripts/index.d.ts", diff --git a/rust-plugins/virtual/src/lib.rs b/rust-plugins/virtual/src/lib.rs index ed0dba3..5dd55cb 100644 --- a/rust-plugins/virtual/src/lib.rs +++ b/rust-plugins/virtual/src/lib.rs @@ -1,103 +1,172 @@ #![deny(clippy::all)] + mod utils; use farmfe_core::{ config::Config, + error::Result, module::ModuleType, plugin::{Plugin, PluginLoadHookResult, PluginResolveHookResult}, serde_json, }; -use std::collections::HashMap; -use std::path::Path; +use std::{collections::HashMap, path::Path, sync::Arc}; +use thiserror::Error; use utils::{normalize_path, path_join}; use farmfe_macro_plugin::farm_plugin; + +const VIRTUAL_PREFIX: &str = "\0virtual:"; + +/// Error types specific to virtual module operations +#[derive(Debug, Error)] +pub enum VirtualModuleError { + #[error("Failed to parse virtual options: {0}")] + OptionsParseError(#[from] serde_json::Error), + #[error("Path operation failed: {0}")] + PathError(#[from] utils::PathError), +} + +/// A plugin that handles virtual modules in the Farm build system #[derive(Debug)] #[farm_plugin] pub struct FarmPluginVirtualModule { - virtual_options: HashMap, - resolved_ids: HashMap, + virtual_modules: HashMap, + resolved_paths: HashMap, } -const PREFIX: &str = "\0virtual:"; + impl FarmPluginVirtualModule { fn new(_: &Config, options: String) -> Self { - let virtual_options = serde_json::from_str::>(&options).unwrap(); - let mut resolved_ids: HashMap = HashMap::new(); - for (module_id, module_content) in &virtual_options { - resolved_ids.insert( - utils::resolve_path(module_id.to_string().clone()), - module_content.clone(), - ); + let virtual_modules:HashMap = + serde_json::from_str(&options).expect("Failed to parse virtual module options"); + + let mut resolved_paths = HashMap::new(); + for (module_id, content) in &virtual_modules { + if let Ok(resolved_path) = utils::resolve_path(module_id) { + resolved_paths.insert(resolved_path, content.clone()); + } } + Self { - virtual_options, - resolved_ids, + virtual_modules, + resolved_paths, + } + } + + /// Checks if a given source is a virtual module + fn is_virtual_module(&self, source: &str) -> bool { + self.virtual_modules.contains_key(source) + } + + /// Resolves a virtual module path + fn resolve_virtual_path(&self, source: &str) -> Option { + if self.is_virtual_module(source) { + Some(PluginResolveHookResult { + resolved_path: format!("{}{}", VIRTUAL_PREFIX, source), + ..Default::default() + }) + } else { + None + } + } + + /// Resolves a relative import within a virtual module + fn resolve_relative_import( + &self, + source: &str, + importer: &str, + root: &str, + ) -> Option { + let importer_path = if importer.starts_with(VIRTUAL_PREFIX) { + &importer[VIRTUAL_PREFIX.len()..] + } else { + importer + }; + + let parts = [root, importer_path]; + let absolute_path = path_join(&parts).ok()?; + + let resolved = Path::new(&absolute_path).with_file_name(source); + let resolved = normalize_path(resolved).to_string_lossy().to_string(); + + if self.resolved_paths.contains_key(&resolved) { + Some(PluginResolveHookResult { + resolved_path: format!("{}{}", VIRTUAL_PREFIX, resolved), + ..Default::default() + }) + } else { + None } } + + /// Loads content for a virtual module + fn load_virtual_module(&self, id: &str) -> Option { + self + .virtual_modules + .get(id) + .or_else(|| self.resolved_paths.get(id)) + .map(|content| PluginLoadHookResult { + content: content.clone(), + module_type: ModuleType::Js, + source_map: None, + }) + } } + impl Plugin for FarmPluginVirtualModule { fn name(&self) -> &str { "FarmPluginVirtual" } + fn resolve( &self, param: &farmfe_core::plugin::PluginResolveHookParam, - context: &std::sync::Arc, + context: &Arc, _hook_context: &farmfe_core::plugin::PluginHookContext, - ) -> farmfe_core::error::Result> { - let root = &context.config.root; - if self.virtual_options.get(¶m.source).is_some() { - let result: PluginResolveHookResult = PluginResolveHookResult { - resolved_path: format!("{}{}", PREFIX, param.source), - ..Default::default() - }; + ) -> Result> { + // Try direct virtual module resolution + if let Some(result) = self.resolve_virtual_path(¶m.source) { return Ok(Some(result)); } - if let Some(id) = ¶m.importer { - let parts = [&root, id.relative_path()]; - let absolute_path = path_join(&parts); - let origin_import = absolute_path.as_str(); - let mut importer_no_prefix = origin_import; - if id.relative_path().starts_with(PREFIX) { - importer_no_prefix = importer_no_prefix.strip_prefix(PREFIX).unwrap(); - } - let resolved = Path::new(importer_no_prefix).with_file_name(¶m.source); - let resolved = normalize_path(resolved).to_string_lossy().to_string(); - if self.resolved_ids.get(resolved.as_str()).is_some() { - return Ok(Some(PluginResolveHookResult { - resolved_path: format!("{}{}", PREFIX, resolved), - ..Default::default() - })); + + // Try relative import resolution + if let Some(importer) = ¶m.importer { + if let Some(result) = self.resolve_relative_import( + ¶m.source, + importer.relative_path(), + &context.config.root, + ) { + return Ok(Some(result)); } } + Ok(None) } + fn load( &self, param: &farmfe_core::plugin::PluginLoadHookParam, - _context: &std::sync::Arc, + _context: &Arc, _hook_context: &farmfe_core::plugin::PluginHookContext, - ) -> farmfe_core::error::Result> { - let resolved_path = param.resolved_path; - if resolved_path.starts_with(PREFIX) { - let id = resolved_path.strip_prefix(PREFIX).unwrap(); - if let Some(value) = self.virtual_options.get(id) { - return Ok(Some(PluginLoadHookResult { - content: value.clone(), - module_type: ModuleType::Js, - source_map: None, - })); - } else { - if let Some(value) = self.resolved_ids.get(id) { - return Ok(Some(PluginLoadHookResult { - content: value.clone(), - module_type: ModuleType::Js, - source_map: None, - })); - } - } + ) -> Result> { + if param.resolved_path.starts_with(VIRTUAL_PREFIX) { + let id = ¶m.resolved_path[VIRTUAL_PREFIX.len()..]; + return Ok(self.load_virtual_module(id)); } Ok(None) } } -// test + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + #[test] + fn test_virtual_module_creation() { + let config = Config::default(); + let options = r#"{"test.js": "console.log('test');"}"#.to_string(); + + let plugin = FarmPluginVirtualModule::new(&config, options); + assert!(plugin.is_virtual_module("test.js")); + } +} diff --git a/rust-plugins/virtual/src/utils.rs b/rust-plugins/virtual/src/utils.rs index e1d83a6..9ec8d96 100644 --- a/rust-plugins/virtual/src/utils.rs +++ b/rust-plugins/virtual/src/utils.rs @@ -1,42 +1,102 @@ use std::{ - env, - path::{Component, Path, PathBuf}, + env, + path::{Component, Path, PathBuf}, + io, }; -pub fn resolve_path(path: String) -> String { - let mut absolute_path = PathBuf::new(); - absolute_path.push(env::current_dir().unwrap()); - absolute_path.push(path); - absolute_path.into_os_string().into_string().unwrap() +/// Error type for path operations +#[derive(Debug, thiserror::Error)] +pub enum PathError { + #[error("IO error: {0}")] + Io(#[from] io::Error), + #[error("Invalid UTF-8 in path")] + InvalidUtf8, } -pub fn path_join(parts: &[&str]) -> String { - let mut path_buf = PathBuf::new(); - for part in parts { - path_buf.push(part); - } - path_buf - .canonicalize() - .unwrap() - .to_string_lossy() - .to_string() + +/// Resolves a relative path to an absolute path using the current working directory +/// +/// # Arguments +/// * `path` - The relative path to resolve +/// +/// # Returns +/// * `Result` - The resolved absolute path as a string +pub fn resolve_path(path: impl AsRef) -> Result { + let current_dir = env::current_dir()?; + let mut absolute_path = current_dir; + absolute_path.push(path.as_ref()); + + absolute_path + .into_os_string() + .into_string() + .map_err(|_| PathError::InvalidUtf8) } + +/// Joins multiple path parts into a single canonical path +/// +/// # Arguments +/// * `parts` - Slice of path parts to join +/// +/// # Returns +/// * `Result` - The joined and canonicalized path as a string +pub fn path_join(parts: &[&str]) -> Result { + let mut path_buf = PathBuf::new(); + for part in parts { + path_buf.push(part); + } + + Ok(path_buf.canonicalize()?.to_string_lossy().into_owned()) +} + +/// Normalizes a path by resolving parent directory references and maintaining trailing slashes +/// +/// # Arguments +/// * `path` - The path to normalize +/// +/// # Returns +/// * `PathBuf` - The normalized path pub fn normalize_path>(path: P) -> PathBuf { - let ends_with_slash = path.as_ref().to_str().map_or(false, |s| s.ends_with('/')); - let mut normalized = PathBuf::new(); - for component in path.as_ref().components() { - match &component { - Component::ParentDir => { - if !normalized.pop() { - normalized.push(component); + let ends_with_slash = path + .as_ref() + .to_str() + .map_or(false, |s| s.ends_with('/')); + + let mut normalized = PathBuf::new(); + for component in path.as_ref().components() { + match component { + Component::ParentDir => { + if !normalized.pop() { + normalized.push(component); + } + } + _ => normalized.push(component), } - } - _ => { - normalized.push(component); - } } - } - if ends_with_slash { - normalized.push(""); - } - normalized + + if ends_with_slash { + normalized.push(""); + } + + normalized +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + + #[test] + fn test_normalize_path() { + assert_eq!( + normalize_path("a/b/../c"), + PathBuf::from("a/c") + ); + assert_eq!( + normalize_path("a/b/../../c"), + PathBuf::from("c") + ); + assert_eq!( + normalize_path("a/b/c/"), + PathBuf::from("a/b/c/") + ); + } }