1use super::Remapping;
2use foundry_compilers_core::utils;
3use rayon::prelude::*;
4use std::{
5 collections::{btree_map::Entry, BTreeMap, HashSet},
6 fs::FileType,
7 path::{Path, PathBuf},
8 sync::Mutex,
9};
10
11const DAPPTOOLS_CONTRACTS_DIR: &str = "src";
12const DAPPTOOLS_LIB_DIR: &str = "lib";
13const JS_CONTRACTS_DIR: &str = "contracts";
14const JS_LIB_DIR: &str = "node_modules";
15
16impl Remapping {
17 pub fn find_many_str(path: &Path) -> Vec<String> {
21 Self::find_many(path).into_iter().map(|r| r.to_string()).collect()
22 }
23
24 #[instrument(level = "trace", name = "Remapping::find_many")]
59 pub fn find_many(dir: &Path) -> Vec<Self> {
60 fn insert_prioritized(
64 mappings: &mut BTreeMap<String, PathBuf>,
65 key: String,
66 path: PathBuf,
67 ) {
68 match mappings.entry(key) {
69 Entry::Occupied(mut e) => {
70 if e.get().components().count() > path.components().count()
71 || (path.ends_with(DAPPTOOLS_CONTRACTS_DIR)
72 && !e.get().ends_with(DAPPTOOLS_CONTRACTS_DIR))
73 {
74 e.insert(path);
75 }
76 }
77 Entry::Vacant(e) => {
78 e.insert(path);
79 }
80 }
81 }
82
83 let is_inside_node_modules = dir.ends_with("node_modules");
84 let visited_symlink_dirs = Mutex::new(HashSet::new());
85
86 let candidates = read_dir(dir)
88 .filter(|(_, file_type, _)| file_type.is_dir())
89 .collect::<Vec<_>>()
90 .par_iter()
91 .flat_map_iter(|(dir, _, _)| {
92 find_remapping_candidates(
93 dir,
94 dir,
95 0,
96 is_inside_node_modules,
97 &visited_symlink_dirs,
98 )
99 })
100 .collect::<Vec<_>>();
101
102 let mut all_remappings = BTreeMap::new();
104 for candidate in candidates {
105 if let Some(name) = candidate.window_start.file_name().and_then(|s| s.to_str()) {
106 insert_prioritized(&mut all_remappings, format!("{name}/"), candidate.source_dir);
107 }
108 }
109
110 all_remappings
111 .into_iter()
112 .map(|(name, path)| Self { context: None, name, path: format!("{}/", path.display()) })
113 .collect()
114 }
115}
116
117#[derive(Clone, Debug)]
118struct Candidate {
119 window_start: PathBuf,
121 source_dir: PathBuf,
123 window_level: usize,
125}
126
127impl Candidate {
128 fn merge_on_same_level(
195 candidates: &mut Vec<Self>,
196 current_dir: &Path,
197 current_level: usize,
198 window_start: PathBuf,
199 is_inside_node_modules: bool,
200 ) {
201 if let Some(pos) = candidates
203 .iter()
204 .enumerate()
205 .fold((0, None), |(mut contracts_dir_count, mut pos), (idx, c)| {
206 if c.source_dir.ends_with(DAPPTOOLS_CONTRACTS_DIR) {
207 contracts_dir_count += 1;
208 if contracts_dir_count == 1 {
209 pos = Some(idx)
210 } else {
211 pos = None;
212 }
213 }
214
215 (contracts_dir_count, pos)
216 })
217 .1
218 {
219 let c = candidates.remove(pos);
220 *candidates = vec![c];
221 } else {
222 candidates.retain(|c| c.window_level != current_level);
226
227 let source_dir = if is_inside_node_modules {
228 window_start.clone()
229 } else {
230 current_dir.to_path_buf()
231 };
232
233 if current_level > 0
236 && source_dir == window_start
237 && (is_source_dir(&source_dir) || is_lib_dir(&source_dir))
238 {
239 return;
240 }
241 candidates.push(Self { window_start, source_dir, window_level: current_level });
242 }
243 }
244
245 fn source_dir_ends_with_js_source(&self) -> bool {
269 self.source_dir.ends_with(JS_CONTRACTS_DIR) || self.source_dir.ends_with("contracts/src/")
270 }
271}
272
273fn is_source_dir(dir: &Path) -> bool {
274 dir.file_name()
275 .and_then(|p| p.to_str())
276 .map(|name| [DAPPTOOLS_CONTRACTS_DIR, JS_CONTRACTS_DIR].contains(&name))
277 .unwrap_or_default()
278}
279
280fn is_lib_dir(dir: &Path) -> bool {
281 dir.file_name()
282 .and_then(|p| p.to_str())
283 .map(|name| [DAPPTOOLS_LIB_DIR, JS_LIB_DIR].contains(&name))
284 .unwrap_or_default()
285}
286
287fn is_hidden(path: &Path) -> bool {
289 path.file_name().and_then(|p| p.to_str()).map(|s| s.starts_with('.')).unwrap_or(false)
290}
291
292fn find_remapping_candidates(
297 current_dir: &Path,
298 open: &Path,
299 current_level: usize,
300 is_inside_node_modules: bool,
301 visited_symlink_dirs: &Mutex<HashSet<PathBuf>>,
302) -> Vec<Candidate> {
303 trace!("find_remapping_candidates({})", current_dir.display());
304
305 let mut is_candidate = false;
307
308 let mut search = Vec::new();
310 for (subdir, file_type, path_is_symlink) in read_dir(current_dir) {
311 if !is_candidate && file_type.is_file() && subdir.extension() == Some("sol".as_ref()) {
313 is_candidate = true;
314 } else if file_type.is_dir() {
315 if path_is_symlink {
324 if let Ok(target) = utils::canonicalize(&subdir) {
325 if !visited_symlink_dirs.lock().unwrap().insert(target.clone()) {
326 return Vec::new();
328 }
329 if open.components().count() > target.components().count()
331 && utils::common_ancestor(open, &target).is_some()
332 {
333 return Vec::new();
335 }
336 }
337 }
338
339 if !no_recurse(&subdir) {
341 search.push(subdir);
342 }
343 }
344 }
345
346 let mut candidates = search
348 .par_iter()
349 .flat_map_iter(|subdir| {
350 if is_lib_dir(subdir) {
359 find_remapping_candidates(
360 subdir,
361 subdir,
362 current_level + 1,
363 is_inside_node_modules,
364 visited_symlink_dirs,
365 )
366 } else {
367 find_remapping_candidates(
369 subdir,
370 open,
371 current_level,
372 is_inside_node_modules,
373 visited_symlink_dirs,
374 )
375 }
376 })
377 .collect::<Vec<_>>();
378
379 let window_start = next_nested_window(open, current_dir);
381 if is_candidate
383 || candidates
384 .iter()
385 .filter(|c| c.window_level == current_level && c.window_start == window_start)
386 .count()
387 > 1
388 {
389 Candidate::merge_on_same_level(
390 &mut candidates,
391 current_dir,
392 current_level,
393 window_start,
394 is_inside_node_modules,
395 );
396 } else {
397 if let Some(candidate) = candidates.iter_mut().find(|c| c.window_level == current_level) {
399 let distance = dir_distance(&candidate.window_start, &candidate.source_dir);
403 if distance > 1 && candidate.source_dir_ends_with_js_source() {
404 candidate.source_dir = window_start;
405 } else if !is_source_dir(&candidate.source_dir)
406 && candidate.source_dir != candidate.window_start
407 {
408 candidate.source_dir = last_nested_source_dir(open, &candidate.source_dir);
409 }
410 }
411 }
412 candidates
413}
414
415fn read_dir(dir: &Path) -> impl Iterator<Item = (PathBuf, FileType, bool)> {
421 std::fs::read_dir(dir)
422 .into_iter()
423 .flatten()
424 .filter_map(Result::ok)
425 .filter_map(|e| {
426 let path = e.path();
427 let mut ft = e.file_type().ok()?;
428 let path_is_symlink = ft.is_symlink();
429 if path_is_symlink {
430 ft = std::fs::metadata(&path).ok()?.file_type();
431 }
432 Some((path, ft, path_is_symlink))
433 })
434 .filter(|(p, _, _)| !is_hidden(p))
435}
436
437fn no_recurse(dir: &Path) -> bool {
438 dir.ends_with("tests") || dir.ends_with("test") || dir.ends_with("demo")
439}
440
441fn dir_distance(root: &Path, current: &Path) -> usize {
444 if root == current {
445 return 0;
446 }
447 if let Ok(rem) = current.strip_prefix(root) {
448 rem.components().count()
449 } else {
450 0
451 }
452}
453
454fn next_nested_window(root: &Path, current: &Path) -> PathBuf {
458 if !is_lib_dir(root) || root == current {
459 return root.to_path_buf();
460 }
461 if let Ok(rem) = current.strip_prefix(root) {
462 let mut p = root.to_path_buf();
463 for c in rem.components() {
464 let next = p.join(c);
465 if !is_lib_dir(&next) || !next.ends_with(JS_CONTRACTS_DIR) {
466 return next;
467 }
468 p = next
469 }
470 }
471 root.to_path_buf()
472}
473
474fn last_nested_source_dir(root: &Path, dir: &Path) -> PathBuf {
476 if is_source_dir(dir) {
477 return dir.to_path_buf();
478 }
479 let mut p = dir;
480 while let Some(parent) = p.parent() {
481 if parent == root {
482 return root.to_path_buf();
483 }
484 if is_source_dir(parent) {
485 return parent.to_path_buf();
486 }
487 p = parent;
488 }
489 root.to_path_buf()
490}
491
492#[cfg(test)]
493mod tests {
494 use super::{super::tests::*, *};
495 use foundry_compilers_core::utils::{mkdir_or_touch, tempdir, touch};
496 use similar_asserts::assert_eq;
497
498 fn to_str(p: std::path::PathBuf) -> String {
500 format!("{}/", p.display())
501 }
502
503 #[test]
504 fn can_determine_nested_window() {
505 let a = Path::new(
506 "/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/lib.Z6ODLZJQeJQa/repo1/lib",
507 );
508 let b = Path::new(
509 "/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/lib.Z6ODLZJQeJQa/repo1/lib/ds-test/src"
510 );
511 assert_eq!(next_nested_window(a, b),Path::new(
512 "/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/lib.Z6ODLZJQeJQa/repo1/lib/ds-test"
513 ));
514 }
515
516 #[test]
517 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
518 fn find_remapping_dapptools() {
519 let tmp_dir = tempdir("lib").unwrap();
520 let tmp_dir_path = tmp_dir.path();
521 let paths = ["repo1/src/", "repo1/src/contract.sol"];
522 mkdir_or_touch(tmp_dir_path, &paths[..]);
523
524 let path = tmp_dir_path.join("repo1").display().to_string();
525 let remappings = Remapping::find_many(tmp_dir_path);
526 assert_eq!(remappings.len(), 1);
528
529 assert_eq!(remappings[0].name, "repo1/");
530 assert_eq!(remappings[0].path, format!("{path}/src/"));
531 }
532
533 #[test]
534 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
535 fn can_resolve_contract_dir_combinations() {
536 let tmp_dir = tempdir("demo").unwrap();
537 let paths =
538 ["lib/timeless/src/lib/A.sol", "lib/timeless/src/B.sol", "lib/timeless/src/test/C.sol"];
539 mkdir_or_touch(tmp_dir.path(), &paths[..]);
540
541 let tmp_dir_path = tmp_dir.path().join("lib");
542 let remappings = Remapping::find_many(&tmp_dir_path);
543 let expected = vec![Remapping {
544 context: None,
545 name: "timeless/".to_string(),
546 path: to_str(tmp_dir_path.join("timeless/src")),
547 }];
548 assert_eq!(remappings, expected);
549 }
550
551 #[test]
552 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
553 fn can_resolve_geb_remappings() {
554 let tmp_dir = tempdir("geb").unwrap();
555 let paths = [
556 "lib/ds-token/src/test/Contract.sol",
557 "lib/ds-token/lib/ds-test/src/Contract.sol",
558 "lib/ds-token/lib/ds-test/aux/Contract.sol",
559 "lib/ds-token/lib/ds-stop/lib/ds-test/src/Contract.sol",
560 "lib/ds-token/lib/ds-stop/lib/ds-note/src/Contract.sol",
561 "lib/ds-token/lib/ds-math/lib/ds-test/aux/Contract.sol",
562 "lib/ds-token/lib/ds-math/src/Contract.sol",
563 "lib/ds-token/lib/ds-stop/lib/ds-test/aux/Contract.sol",
564 "lib/ds-token/lib/ds-stop/lib/ds-note/lib/ds-test/src/Contract.sol",
565 "lib/ds-token/lib/ds-math/lib/ds-test/src/Contract.sol",
566 "lib/ds-token/lib/ds-stop/lib/ds-auth/lib/ds-test/src/Contract.sol",
567 "lib/ds-token/lib/ds-stop/src/Contract.sol",
568 "lib/ds-token/src/Contract.sol",
569 "lib/ds-token/lib/erc20/src/Contract.sol",
570 "lib/ds-token/lib/ds-stop/lib/ds-auth/lib/ds-test/aux/Contract.sol",
571 "lib/ds-token/lib/ds-stop/lib/ds-auth/src/Contract.sol",
572 "lib/ds-token/lib/ds-stop/lib/ds-note/lib/ds-test/aux/Contract.sol",
573 ];
574 mkdir_or_touch(tmp_dir.path(), &paths[..]);
575
576 let tmp_dir_path = tmp_dir.path().join("lib");
577 let mut remappings = Remapping::find_many(&tmp_dir_path);
578 remappings.sort_unstable();
579 let mut expected = vec![
580 Remapping {
581 context: None,
582 name: "ds-auth/".to_string(),
583 path: to_str(tmp_dir_path.join("ds-token/lib/ds-stop/lib/ds-auth/src")),
584 },
585 Remapping {
586 context: None,
587 name: "ds-math/".to_string(),
588 path: to_str(tmp_dir_path.join("ds-token/lib/ds-math/src")),
589 },
590 Remapping {
591 context: None,
592 name: "ds-note/".to_string(),
593 path: to_str(tmp_dir_path.join("ds-token/lib/ds-stop/lib/ds-note/src")),
594 },
595 Remapping {
596 context: None,
597 name: "ds-stop/".to_string(),
598 path: to_str(tmp_dir_path.join("ds-token/lib/ds-stop/src")),
599 },
600 Remapping {
601 context: None,
602 name: "ds-test/".to_string(),
603 path: to_str(tmp_dir_path.join("ds-token/lib/ds-test/src")),
604 },
605 Remapping {
606 context: None,
607 name: "ds-token/".to_string(),
608 path: to_str(tmp_dir_path.join("ds-token/src")),
609 },
610 Remapping {
611 context: None,
612 name: "erc20/".to_string(),
613 path: to_str(tmp_dir_path.join("ds-token/lib/erc20/src")),
614 },
615 ];
616 expected.sort_unstable();
617 assert_eq!(remappings, expected);
618 }
619
620 #[test]
621 fn can_resolve_nested_chainlink_remappings() {
622 let tmp_dir = tempdir("root").unwrap();
623 let paths = [
624 "@chainlink/contracts/src/v0.6/vendor/Contract.sol",
625 "@chainlink/contracts/src/v0.8/tests/Contract.sol",
626 "@chainlink/contracts/src/v0.7/Contract.sol",
627 "@chainlink/contracts/src/v0.6/Contract.sol",
628 "@chainlink/contracts/src/v0.5/Contract.sol",
629 "@chainlink/contracts/src/v0.7/tests/Contract.sol",
630 "@chainlink/contracts/src/v0.7/interfaces/Contract.sol",
631 "@chainlink/contracts/src/v0.4/tests/Contract.sol",
632 "@chainlink/contracts/src/v0.6/tests/Contract.sol",
633 "@chainlink/contracts/src/v0.5/tests/Contract.sol",
634 "@chainlink/contracts/src/v0.8/vendor/Contract.sol",
635 "@chainlink/contracts/src/v0.5/dev/Contract.sol",
636 "@chainlink/contracts/src/v0.6/examples/Contract.sol",
637 "@chainlink/contracts/src/v0.5/interfaces/Contract.sol",
638 "@chainlink/contracts/src/v0.4/interfaces/Contract.sol",
639 "@chainlink/contracts/src/v0.4/vendor/Contract.sol",
640 "@chainlink/contracts/src/v0.6/interfaces/Contract.sol",
641 "@chainlink/contracts/src/v0.7/dev/Contract.sol",
642 "@chainlink/contracts/src/v0.8/dev/Contract.sol",
643 "@chainlink/contracts/src/v0.5/vendor/Contract.sol",
644 "@chainlink/contracts/src/v0.7/vendor/Contract.sol",
645 "@chainlink/contracts/src/v0.4/Contract.sol",
646 "@chainlink/contracts/src/v0.8/interfaces/Contract.sol",
647 "@chainlink/contracts/src/v0.6/dev/Contract.sol",
648 ];
649 mkdir_or_touch(tmp_dir.path(), &paths[..]);
650 let remappings = Remapping::find_many(tmp_dir.path());
651
652 let expected = vec![Remapping {
653 context: None,
654 name: "@chainlink/".to_string(),
655 path: to_str(tmp_dir.path().join("@chainlink")),
656 }];
657 assert_eq!(remappings, expected);
658 }
659
660 #[test]
661 fn can_resolve_oz_upgradeable_remappings() {
662 let tmp_dir = tempdir("root").unwrap();
663 let paths = [
664 "@openzeppelin/contracts-upgradeable/proxy/ERC1967/Contract.sol",
665 "@openzeppelin/contracts-upgradeable/token/ERC1155/Contract.sol",
666 "@openzeppelin/contracts/token/ERC777/Contract.sol",
667 "@openzeppelin/contracts/token/ERC721/presets/Contract.sol",
668 "@openzeppelin/contracts/interfaces/Contract.sol",
669 "@openzeppelin/contracts-upgradeable/token/ERC777/presets/Contract.sol",
670 "@openzeppelin/contracts/token/ERC1155/extensions/Contract.sol",
671 "@openzeppelin/contracts/proxy/Contract.sol",
672 "@openzeppelin/contracts/proxy/utils/Contract.sol",
673 "@openzeppelin/contracts-upgradeable/security/Contract.sol",
674 "@openzeppelin/contracts-upgradeable/utils/Contract.sol",
675 "@openzeppelin/contracts/token/ERC20/Contract.sol",
676 "@openzeppelin/contracts-upgradeable/utils/introspection/Contract.sol",
677 "@openzeppelin/contracts/metatx/Contract.sol",
678 "@openzeppelin/contracts/utils/cryptography/Contract.sol",
679 "@openzeppelin/contracts/token/ERC20/utils/Contract.sol",
680 "@openzeppelin/contracts-upgradeable/token/ERC20/utils/Contract.sol",
681 "@openzeppelin/contracts-upgradeable/proxy/Contract.sol",
682 "@openzeppelin/contracts-upgradeable/token/ERC20/presets/Contract.sol",
683 "@openzeppelin/contracts-upgradeable/utils/math/Contract.sol",
684 "@openzeppelin/contracts-upgradeable/utils/escrow/Contract.sol",
685 "@openzeppelin/contracts/governance/extensions/Contract.sol",
686 "@openzeppelin/contracts-upgradeable/interfaces/Contract.sol",
687 "@openzeppelin/contracts/proxy/transparent/Contract.sol",
688 "@openzeppelin/contracts/utils/structs/Contract.sol",
689 "@openzeppelin/contracts-upgradeable/access/Contract.sol",
690 "@openzeppelin/contracts/governance/compatibility/Contract.sol",
691 "@openzeppelin/contracts/governance/Contract.sol",
692 "@openzeppelin/contracts-upgradeable/governance/extensions/Contract.sol",
693 "@openzeppelin/contracts/security/Contract.sol",
694 "@openzeppelin/contracts-upgradeable/metatx/Contract.sol",
695 "@openzeppelin/contracts-upgradeable/token/ERC721/utils/Contract.sol",
696 "@openzeppelin/contracts/token/ERC721/utils/Contract.sol",
697 "@openzeppelin/contracts-upgradeable/governance/compatibility/Contract.sol",
698 "@openzeppelin/contracts/token/common/Contract.sol",
699 "@openzeppelin/contracts/proxy/beacon/Contract.sol",
700 "@openzeppelin/contracts-upgradeable/token/ERC721/Contract.sol",
701 "@openzeppelin/contracts-upgradeable/proxy/beacon/Contract.sol",
702 "@openzeppelin/contracts/token/ERC1155/utils/Contract.sol",
703 "@openzeppelin/contracts/token/ERC777/presets/Contract.sol",
704 "@openzeppelin/contracts-upgradeable/token/ERC20/Contract.sol",
705 "@openzeppelin/contracts-upgradeable/utils/structs/Contract.sol",
706 "@openzeppelin/contracts/utils/escrow/Contract.sol",
707 "@openzeppelin/contracts/utils/Contract.sol",
708 "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/Contract.sol",
709 "@openzeppelin/contracts/token/ERC721/extensions/Contract.sol",
710 "@openzeppelin/contracts-upgradeable/token/ERC777/Contract.sol",
711 "@openzeppelin/contracts/token/ERC1155/presets/Contract.sol",
712 "@openzeppelin/contracts/token/ERC721/Contract.sol",
713 "@openzeppelin/contracts/token/ERC1155/Contract.sol",
714 "@openzeppelin/contracts-upgradeable/governance/Contract.sol",
715 "@openzeppelin/contracts/token/ERC20/extensions/Contract.sol",
716 "@openzeppelin/contracts-upgradeable/utils/cryptography/Contract.sol",
717 "@openzeppelin/contracts-upgradeable/token/ERC1155/presets/Contract.sol",
718 "@openzeppelin/contracts/access/Contract.sol",
719 "@openzeppelin/contracts/governance/utils/Contract.sol",
720 "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/Contract.sol",
721 "@openzeppelin/contracts-upgradeable/token/common/Contract.sol",
722 "@openzeppelin/contracts-upgradeable/token/ERC1155/utils/Contract.sol",
723 "@openzeppelin/contracts/proxy/ERC1967/Contract.sol",
724 "@openzeppelin/contracts/finance/Contract.sol",
725 "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/Contract.sol",
726 "@openzeppelin/contracts-upgradeable/governance/utils/Contract.sol",
727 "@openzeppelin/contracts-upgradeable/proxy/utils/Contract.sol",
728 "@openzeppelin/contracts/token/ERC20/presets/Contract.sol",
729 "@openzeppelin/contracts/utils/math/Contract.sol",
730 "@openzeppelin/contracts-upgradeable/token/ERC721/presets/Contract.sol",
731 "@openzeppelin/contracts-upgradeable/finance/Contract.sol",
732 "@openzeppelin/contracts/utils/introspection/Contract.sol",
733 ];
734 mkdir_or_touch(tmp_dir.path(), &paths[..]);
735 let remappings = Remapping::find_many(tmp_dir.path());
736
737 let expected = vec![Remapping {
738 context: None,
739 name: "@openzeppelin/".to_string(),
740 path: to_str(tmp_dir.path().join("@openzeppelin")),
741 }];
742 assert_eq!(remappings, expected);
743 }
744
745 #[test]
746 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
747 fn recursive_remappings() {
748 let tmp_dir = tempdir("lib").unwrap();
749 let tmp_dir_path = tmp_dir.path();
750 let paths = [
751 "repo1/src/contract.sol",
752 "repo1/lib/ds-test/src/test.sol",
753 "repo1/lib/ds-math/src/contract.sol",
754 "repo1/lib/ds-math/lib/ds-test/src/test.sol",
755 "repo1/lib/guni-lev/src/contract.sol",
756 "repo1/lib/solmate/src/auth/contract.sol",
757 "repo1/lib/solmate/src/tokens/contract.sol",
758 "repo1/lib/solmate/lib/ds-test/src/test.sol",
759 "repo1/lib/solmate/lib/ds-test/demo/demo.sol",
760 "repo1/lib/openzeppelin-contracts/contracts/access/AccessControl.sol",
761 "repo1/lib/ds-token/lib/ds-stop/src/contract.sol",
762 "repo1/lib/ds-token/lib/ds-stop/lib/ds-note/src/contract.sol",
763 ];
764 mkdir_or_touch(tmp_dir_path, &paths[..]);
765
766 let mut remappings = Remapping::find_many(tmp_dir_path);
767 remappings.sort_unstable();
768
769 let mut expected = vec![
770 Remapping {
771 context: None,
772 name: "repo1/".to_string(),
773 path: to_str(tmp_dir_path.join("repo1").join("src")),
774 },
775 Remapping {
776 context: None,
777 name: "ds-math/".to_string(),
778 path: to_str(tmp_dir_path.join("repo1").join("lib").join("ds-math").join("src")),
779 },
780 Remapping {
781 context: None,
782 name: "ds-test/".to_string(),
783 path: to_str(tmp_dir_path.join("repo1").join("lib").join("ds-test").join("src")),
784 },
785 Remapping {
786 context: None,
787 name: "guni-lev/".to_string(),
788 path: to_str(tmp_dir_path.join("repo1/lib/guni-lev").join("src")),
789 },
790 Remapping {
791 context: None,
792 name: "solmate/".to_string(),
793 path: to_str(tmp_dir_path.join("repo1/lib/solmate").join("src")),
794 },
795 Remapping {
796 context: None,
797 name: "openzeppelin-contracts/".to_string(),
798 path: to_str(tmp_dir_path.join("repo1/lib/openzeppelin-contracts/contracts")),
799 },
800 Remapping {
801 context: None,
802 name: "ds-stop/".to_string(),
803 path: to_str(tmp_dir_path.join("repo1/lib/ds-token/lib/ds-stop/src")),
804 },
805 Remapping {
806 context: None,
807 name: "ds-note/".to_string(),
808 path: to_str(tmp_dir_path.join("repo1/lib/ds-token/lib/ds-stop/lib/ds-note/src")),
809 },
810 ];
811 expected.sort_unstable();
812 assert_eq!(remappings, expected);
813 }
814
815 #[test]
816 fn remappings() {
817 let tmp_dir = tempdir("tmp").unwrap();
818 let tmp_dir_path = tmp_dir.path().join("lib");
819 let repo1 = tmp_dir_path.join("src_repo");
820 let repo2 = tmp_dir_path.join("contracts_repo");
821
822 let dir1 = repo1.join("src");
823 std::fs::create_dir_all(&dir1).unwrap();
824
825 let dir2 = repo2.join("contracts");
826 std::fs::create_dir_all(&dir2).unwrap();
827
828 let contract1 = dir1.join("contract.sol");
829 touch(&contract1).unwrap();
830
831 let contract2 = dir2.join("contract.sol");
832 touch(&contract2).unwrap();
833
834 let mut remappings = Remapping::find_many(&tmp_dir_path);
835 remappings.sort_unstable();
836 let mut expected = vec![
837 Remapping {
838 context: None,
839 name: "src_repo/".to_string(),
840 path: format!("{}/", dir1.into_os_string().into_string().unwrap()),
841 },
842 Remapping {
843 context: None,
844 name: "contracts_repo/".to_string(),
845 path: format!(
846 "{}/",
847 repo2.join("contracts").into_os_string().into_string().unwrap()
848 ),
849 },
850 ];
851 expected.sort_unstable();
852 assert_eq!(remappings, expected);
853 }
854
855 #[test]
856 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
857 fn simple_dapptools_remappings() {
858 let tmp_dir = tempdir("lib").unwrap();
859 let tmp_dir_path = tmp_dir.path();
860 let paths = [
861 "ds-test/src",
862 "ds-test/demo",
863 "ds-test/demo/demo.sol",
864 "ds-test/src/test.sol",
865 "openzeppelin/src/interfaces/c.sol",
866 "openzeppelin/src/token/ERC/c.sol",
867 "standards/src/interfaces/iweth.sol",
868 "uniswapv2/src",
869 ];
870 mkdir_or_touch(tmp_dir_path, &paths[..]);
871
872 let mut remappings = Remapping::find_many(tmp_dir_path);
873 remappings.sort_unstable();
874
875 let mut expected = vec![
876 Remapping {
877 context: None,
878 name: "ds-test/".to_string(),
879 path: to_str(tmp_dir_path.join("ds-test/src")),
880 },
881 Remapping {
882 context: None,
883 name: "openzeppelin/".to_string(),
884 path: to_str(tmp_dir_path.join("openzeppelin/src")),
885 },
886 Remapping {
887 context: None,
888 name: "standards/".to_string(),
889 path: to_str(tmp_dir_path.join("standards/src")),
890 },
891 ];
892 expected.sort_unstable();
893 assert_eq!(remappings, expected);
894 }
895
896 #[test]
897 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
898 fn hardhat_remappings() {
899 let tmp_dir = tempdir("node_modules").unwrap();
900 let tmp_dir_node_modules = tmp_dir.path().join("node_modules");
901 let paths = [
902 "node_modules/@aave/aave-token/contracts/token/AaveToken.sol",
903 "node_modules/@aave/governance-v2/contracts/governance/Executor.sol",
904 "node_modules/@aave/protocol-v2/contracts/protocol/lendingpool/",
905 "node_modules/@aave/protocol-v2/contracts/protocol/lendingpool/LendingPool.sol",
906 "node_modules/@ensdomains/ens/contracts/contract.sol",
907 "node_modules/prettier-plugin-solidity/tests/format/ModifierDefinitions/",
908 "node_modules/prettier-plugin-solidity/tests/format/ModifierDefinitions/
909 ModifierDefinitions.sol",
910 "node_modules/@openzeppelin/contracts/tokens/contract.sol",
911 "node_modules/@openzeppelin/contracts/access/contract.sol",
912 "node_modules/eth-gas-reporter/mock/contracts/ConvertLib.sol",
913 "node_modules/eth-gas-reporter/mock/test/TestMetacoin.sol",
914 ];
915 mkdir_or_touch(tmp_dir.path(), &paths[..]);
916 let mut remappings = Remapping::find_many(&tmp_dir_node_modules);
917 remappings.sort_unstable();
918 let mut expected = vec![
919 Remapping {
920 context: None,
921 name: "@aave/".to_string(),
922 path: to_str(tmp_dir_node_modules.join("@aave")),
923 },
924 Remapping {
925 context: None,
926 name: "@ensdomains/".to_string(),
927 path: to_str(tmp_dir_node_modules.join("@ensdomains")),
928 },
929 Remapping {
930 context: None,
931 name: "@openzeppelin/".to_string(),
932 path: to_str(tmp_dir_node_modules.join("@openzeppelin")),
933 },
934 Remapping {
935 context: None,
936 name: "eth-gas-reporter/".to_string(),
937 path: to_str(tmp_dir_node_modules.join("eth-gas-reporter")),
938 },
939 ];
940 expected.sort_unstable();
941 assert_eq!(remappings, expected);
942 }
943
944 #[test]
945 #[cfg_attr(windows, ignore = "Windows remappings #2347")]
946 fn find_openzeppelin_remapping() {
947 let tmp_dir = tempdir("lib").unwrap();
948 let tmp_dir_path = tmp_dir.path();
949 let paths = [
950 "lib/ds-test/src/test.sol",
951 "lib/forge-std/src/test.sol",
952 "openzeppelin/contracts/interfaces/c.sol",
953 ];
954 mkdir_or_touch(tmp_dir_path, &paths[..]);
955
956 let path = tmp_dir_path.display().to_string();
957 let mut remappings = Remapping::find_many(path.as_ref());
958 remappings.sort_unstable();
959
960 let mut expected = vec![
961 Remapping {
962 context: None,
963 name: "ds-test/".to_string(),
964 path: to_str(tmp_dir_path.join("lib/ds-test/src")),
965 },
966 Remapping {
967 context: None,
968 name: "openzeppelin/".to_string(),
969 path: to_str(tmp_dir_path.join("openzeppelin/contracts")),
970 },
971 Remapping {
972 context: None,
973 name: "forge-std/".to_string(),
974 path: to_str(tmp_dir_path.join("lib/forge-std/src")),
975 },
976 ];
977 expected.sort_unstable();
978 assert_eq!(remappings, expected);
979 }
980}