1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
use std::fs::read_to_string; use std::path::{Component, Path, PathBuf}; pub fn resolve(name: String, context: &Path) -> Option<PathBuf> { let path = Path::new(&name); if path.starts_with("./") || path.starts_with("../") { let new_path = normalize(&context.join(path)); load(&new_path) } else if path.is_absolute() { load(path) } else if name.is_empty() { load(context) } else { let parent = context.parent()?; let new_path = context.join("node_modules").join(&path); load(&new_path).or(resolve(name, parent)) } } fn load(path: &Path) -> Option<PathBuf> { if path.is_file() { return Some(path.to_path_buf()); } let extensions = vec!["js", "mjs", "json"]; for extension in extensions { let new_path = path.with_extension(extension); if new_path.is_file() { return Some(new_path); } } let pkg_path = path.join("package.json"); if let Ok(data) = read_to_string(&pkg_path) { if let Ok(pkg_info) = json::parse(&data) { if let Some(main) = pkg_info["main"].as_str() { if main != "." && main != ".." { return load(&path.join(main)); } } } } if path.is_dir() { return load(&path.join("index")); } None } fn normalize(p: &Path) -> PathBuf { p.components().fold(PathBuf::from("/"), |path, c| match c { Component::Prefix(ref prefix) => PathBuf::from(prefix.as_os_str().to_owned()), Component::RootDir => path.join("/"), Component::CurDir => path, Component::ParentDir => path.parent().unwrap().to_owned(), Component::Normal(part) => path.join(part), }) } #[test] fn test_resolve() { fn assert_resolves(name: &str, path: &str, expected: &str) { let fixtures = std::env::current_dir().unwrap().join("fixtures"); assert_eq!(resolve(name.to_string(), &fixtures.join(path)), Some( normalize(&fixtures.join(path).join(expected.to_string())) )); } assert_resolves("", "no-entry", "index.js"); assert_resolves("./counter", "relative-file", "counter"); assert_resolves("./counter", "relative-file-js", "counter.js"); assert_resolves("./counter", "relative-file-mjs", "counter.mjs"); assert_resolves("./counter/counter", "relative-file-nested", "counter/counter.js"); assert_resolves("./😅", "relative-file-unicode", "😅.js"); assert_resolves("./😅", "relative-dir-unicode", "😅/index.js"); assert_resolves("./😅/🤔", "relative-nested-unicode", "😅/🤔.js"); assert_resolves("../counter", "parent-dir/entry", "../counter/index.js"); assert_resolves("../counter", "parent-js/entry", "../counter.js"); assert_resolves("../counter/counter", "parent-nested/entry", "../counter/counter.js"); assert_resolves("./counter", "subdir", "counter/index.js"); assert_resolves("./counter", "subdir-noext", "counter/index"); assert_resolves("./", "pkginfo-basic", "counter.js"); assert_resolves(".", "pkginfo-basic", "counter.js"); assert_resolves("./counter", "pkginfo-nested", "counter/counter.js"); assert_resolves("../", "pkginfo-parent/entry", "../counter.js"); assert_resolves("..", "pkginfo-parent/entry", "../counter.js"); assert_resolves(".", "pkginfo-dot", "index.js"); assert_resolves("..", "pkginfo-dot/entry", "../index.js"); assert_resolves("package", "modules-basic", "node_modules/package/index.js"); assert_resolves("package", "modules-file", "node_modules/package.js"); assert_resolves("package", "modules-pkginfo", "node_modules/package/entry.js"); assert_resolves("package", "modules-pkginfo-relative", "node_modules/package/lib/index.js"); assert_resolves("package/lib/counter", "modules-nested", "node_modules/package/lib/counter.js"); assert_resolves(".package", "modules-dotted", "node_modules/.package/index.js"); assert_resolves("counter", "modules-parent/subdir", "../node_modules/counter/index.js"); assert_resolves("counter", "modules-multilevels/subdir/subdir/subdir/subdir", "../../../../node_modules/counter/index.js"); assert_resolves("😅", "unicode-pkg", "node_modules/😅/index.js"); assert_resolves("package", "unicode-pkg-entry", "node_modules/package/🤔.js"); assert_resolves("🤔", "unicode-both", "node_modules/🤔/😅"); } #[test] fn test_normalize() { assert_eq!( normalize(&Path::new("/Users/shf/Projects").join(Path::new("/Users/shf/Projects/paq"))), PathBuf::from("/Users/shf/Projects/paq") ); assert_eq!( normalize(&Path::new("/Users/shf/Projects").join(Path::new("paq"))), PathBuf::from("/Users/shf/Projects/paq") ); }