use super::Remapping;
use foundry_compilers_core::utils;
use rayon::prelude::*;
use std::{
collections::{BTreeMap, HashSet, btree_map::Entry},
fs::FileType,
path::{Path, PathBuf},
sync::Mutex,
};
const DAPPTOOLS_CONTRACTS_DIR: &str = "src";
const DAPPTOOLS_LIB_DIR: &str = "lib";
const JS_CONTRACTS_DIR: &str = "contracts";
const JS_LIB_DIR: &str = "node_modules";
impl Remapping {
pub fn find_many_str(path: &Path) -> Vec<String> {
Self::find_many(path).into_iter().map(|r| r.to_string()).collect()
}
#[instrument(level = "trace", name = "Remapping::find_many")]
pub fn find_many(dir: &Path) -> Vec<Self> {
fn insert_prioritized(
mappings: &mut BTreeMap<String, PathBuf>,
key: String,
path: PathBuf,
) {
match mappings.entry(key) {
Entry::Occupied(mut e) => {
if e.get().components().count() > path.components().count()
|| (path.ends_with(DAPPTOOLS_CONTRACTS_DIR)
&& !e.get().ends_with(DAPPTOOLS_CONTRACTS_DIR))
{
e.insert(path);
}
}
Entry::Vacant(e) => {
e.insert(path);
}
}
}
let is_inside_node_modules = dir.ends_with("node_modules");
let visited_symlink_dirs = Mutex::new(HashSet::new());
let mut candidates = read_dir(dir)
.filter(|(_, file_type, _)| file_type.is_dir())
.collect::<Vec<_>>()
.par_iter()
.flat_map_iter(|(dir, _, _)| {
find_remapping_candidates(
dir,
dir,
0,
is_inside_node_modules,
&visited_symlink_dirs,
)
})
.collect::<Vec<_>>();
candidates.sort_by(|a, b| a.source_dir.cmp(&b.source_dir));
let mut all_remappings = BTreeMap::new();
for candidate in candidates {
if let Some(name) = candidate.window_start.file_name().and_then(|s| s.to_str()) {
insert_prioritized(&mut all_remappings, format!("{name}/"), candidate.source_dir);
}
}
all_remappings
.into_iter()
.map(|(name, path)| Self { context: None, name, path: format!("{}/", path.display()) })
.collect()
}
}
#[derive(Clone, Debug)]
struct Candidate {
window_start: PathBuf,
source_dir: PathBuf,
window_level: usize,
}
impl Candidate {
fn merge_on_same_level(
candidates: &mut Vec<Self>,
current_dir: &Path,
current_level: usize,
window_start: PathBuf,
is_inside_node_modules: bool,
) {
if let Some(pos) = candidates
.iter()
.enumerate()
.fold((0, None), |(mut contracts_dir_count, mut pos), (idx, c)| {
if c.source_dir.ends_with(DAPPTOOLS_CONTRACTS_DIR) {
contracts_dir_count += 1;
if contracts_dir_count == 1 {
pos = Some(idx)
} else {
pos = None;
}
}
(contracts_dir_count, pos)
})
.1
{
let c = candidates.remove(pos);
*candidates = vec![c];
} else {
candidates.retain(|c| c.window_level != current_level);
let source_dir = if is_inside_node_modules {
window_start.clone()
} else {
current_dir.to_path_buf()
};
if current_level > 0
&& source_dir == window_start
&& (is_source_dir(&source_dir) || is_lib_dir(&source_dir))
{
return;
}
candidates.push(Self { window_start, source_dir, window_level: current_level });
}
}
fn source_dir_ends_with_js_source(&self) -> bool {
self.source_dir.ends_with(JS_CONTRACTS_DIR) || self.source_dir.ends_with("contracts/src/")
}
}
fn is_source_dir(dir: &Path) -> bool {
dir.file_name()
.and_then(|p| p.to_str())
.map(|name| [DAPPTOOLS_CONTRACTS_DIR, JS_CONTRACTS_DIR].contains(&name))
.unwrap_or_default()
}
fn is_lib_dir(dir: &Path) -> bool {
dir.file_name()
.and_then(|p| p.to_str())
.map(|name| [DAPPTOOLS_LIB_DIR, JS_LIB_DIR].contains(&name))
.unwrap_or_default()
}
fn is_hidden(path: &Path) -> bool {
path.file_name().and_then(|p| p.to_str()).map(|s| s.starts_with('.')).unwrap_or(false)
}
fn find_remapping_candidates(
current_dir: &Path,
open: &Path,
current_level: usize,
is_inside_node_modules: bool,
visited_symlink_dirs: &Mutex<HashSet<PathBuf>>,
) -> Vec<Candidate> {
trace!("find_remapping_candidates({})", current_dir.display());
let mut is_candidate = false;
let mut search = Vec::new();
for (subdir, file_type, path_is_symlink) in read_dir(current_dir) {
if !is_candidate && file_type.is_file() && subdir.extension() == Some("sol".as_ref()) {
is_candidate = true;
} else if file_type.is_dir() {
if path_is_symlink && let Ok(target) = utils::canonicalize(&subdir) {
if !visited_symlink_dirs.lock().unwrap().insert(target.clone()) {
return Vec::new();
}
if open.components().count() > target.components().count()
&& utils::common_ancestor(open, &target).is_some()
{
return Vec::new();
}
}
if !no_recurse(&subdir) {
search.push(subdir);
}
}
}
let mut candidates = search
.par_iter()
.flat_map_iter(|subdir| {
if is_lib_dir(subdir) {
find_remapping_candidates(
subdir,
subdir,
current_level + 1,
is_inside_node_modules,
visited_symlink_dirs,
)
} else {
find_remapping_candidates(
subdir,
open,
current_level,
is_inside_node_modules,
visited_symlink_dirs,
)
}
})
.collect::<Vec<_>>();
let window_start = next_nested_window(open, current_dir);
if is_candidate
|| candidates
.iter()
.filter(|c| c.window_level == current_level && c.window_start == window_start)
.count()
> 1
{
Candidate::merge_on_same_level(
&mut candidates,
current_dir,
current_level,
window_start,
is_inside_node_modules,
);
} else {
if let Some(candidate) = candidates.iter_mut().find(|c| c.window_level == current_level) {
let distance = dir_distance(&candidate.window_start, &candidate.source_dir);
if distance > 1 && candidate.source_dir_ends_with_js_source() {
candidate.source_dir = window_start;
} else if !is_source_dir(&candidate.source_dir)
&& candidate.source_dir != candidate.window_start
{
candidate.source_dir = last_nested_source_dir(open, &candidate.source_dir);
}
}
}
candidates
}
fn read_dir(dir: &Path) -> impl Iterator<Item = (PathBuf, FileType, bool)> {
std::fs::read_dir(dir)
.into_iter()
.flatten()
.filter_map(Result::ok)
.filter_map(|e| {
let path = e.path();
let mut ft = e.file_type().ok()?;
let path_is_symlink = ft.is_symlink();
if path_is_symlink {
ft = std::fs::metadata(&path).ok()?.file_type();
}
Some((path, ft, path_is_symlink))
})
.filter(|(p, _, _)| !is_hidden(p))
}
fn no_recurse(dir: &Path) -> bool {
dir.ends_with("tests") || dir.ends_with("test") || dir.ends_with("demo")
}
fn dir_distance(root: &Path, current: &Path) -> usize {
if root == current {
return 0;
}
if let Ok(rem) = current.strip_prefix(root) { rem.components().count() } else { 0 }
}
fn next_nested_window(root: &Path, current: &Path) -> PathBuf {
if !is_lib_dir(root) || root == current {
return root.to_path_buf();
}
if let Ok(rem) = current.strip_prefix(root) {
let mut p = root.to_path_buf();
for c in rem.components() {
let next = p.join(c);
if !is_lib_dir(&next) || !next.ends_with(JS_CONTRACTS_DIR) {
return next;
}
p = next
}
}
root.to_path_buf()
}
fn last_nested_source_dir(root: &Path, dir: &Path) -> PathBuf {
if is_source_dir(dir) {
return dir.to_path_buf();
}
let mut p = dir;
while let Some(parent) = p.parent() {
if parent == root {
return root.to_path_buf();
}
if is_source_dir(parent) {
return parent.to_path_buf();
}
p = parent;
}
root.to_path_buf()
}
#[cfg(test)]
mod tests {
use super::{super::tests::*, *};
use foundry_compilers_core::utils::{mkdir_or_touch, tempdir, touch};
fn to_str(p: std::path::PathBuf) -> String {
format!("{}/", p.display())
}
#[test]
fn can_determine_nested_window() {
let a = Path::new(
"/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/lib.Z6ODLZJQeJQa/repo1/lib",
);
let b = Path::new(
"/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/lib.Z6ODLZJQeJQa/repo1/lib/ds-test/src",
);
assert_eq!(
next_nested_window(a, b),
Path::new(
"/var/folders/l5/lprhf87s6xv8djgd017f0b2h0000gn/T/lib.Z6ODLZJQeJQa/repo1/lib/ds-test"
)
);
}
#[test]
#[cfg_attr(windows, ignore = "Windows remappings #2347")]
fn find_remapping_dapptools() {
let tmp_dir = tempdir("lib").unwrap();
let tmp_dir_path = tmp_dir.path();
let paths = ["repo1/src/", "repo1/src/contract.sol"];
mkdir_or_touch(tmp_dir_path, &paths[..]);
let path = tmp_dir_path.join("repo1").display().to_string();
let remappings = Remapping::find_many(tmp_dir_path);
assert_eq!(remappings.len(), 1);
assert_eq!(remappings[0].name, "repo1/");
assert_eq!(remappings[0].path, format!("{path}/src/"));
}
#[test]
#[cfg_attr(windows, ignore = "Windows remappings #2347")]
fn can_resolve_contract_dir_combinations() {
let tmp_dir = tempdir("demo").unwrap();
let paths =
["lib/timeless/src/lib/A.sol", "lib/timeless/src/B.sol", "lib/timeless/src/test/C.sol"];
mkdir_or_touch(tmp_dir.path(), &paths[..]);
let tmp_dir_path = tmp_dir.path().join("lib");
let remappings = Remapping::find_many(&tmp_dir_path);
let expected = vec![Remapping {
context: None,
name: "timeless/".to_string(),
path: to_str(tmp_dir_path.join("timeless/src")),
}];
assert_eq!(remappings, expected);
}
#[test]
#[cfg_attr(windows, ignore = "Windows remappings #2347")]
fn can_resolve_geb_remappings() {
let tmp_dir = tempdir("geb").unwrap();
let paths = [
"lib/ds-token/src/test/Contract.sol",
"lib/ds-token/lib/ds-test/src/Contract.sol",
"lib/ds-token/lib/ds-test/aux/Contract.sol",
"lib/ds-token/lib/ds-stop/lib/ds-test/src/Contract.sol",
"lib/ds-token/lib/ds-stop/lib/ds-note/src/Contract.sol",
"lib/ds-token/lib/ds-math/lib/ds-test/aux/Contract.sol",
"lib/ds-token/lib/ds-math/src/Contract.sol",
"lib/ds-token/lib/ds-stop/lib/ds-test/aux/Contract.sol",
"lib/ds-token/lib/ds-stop/lib/ds-note/lib/ds-test/src/Contract.sol",
"lib/ds-token/lib/ds-math/lib/ds-test/src/Contract.sol",
"lib/ds-token/lib/ds-stop/lib/ds-auth/lib/ds-test/src/Contract.sol",
"lib/ds-token/lib/ds-stop/src/Contract.sol",
"lib/ds-token/src/Contract.sol",
"lib/ds-token/lib/erc20/src/Contract.sol",
"lib/ds-token/lib/ds-stop/lib/ds-auth/lib/ds-test/aux/Contract.sol",
"lib/ds-token/lib/ds-stop/lib/ds-auth/src/Contract.sol",
"lib/ds-token/lib/ds-stop/lib/ds-note/lib/ds-test/aux/Contract.sol",
];
mkdir_or_touch(tmp_dir.path(), &paths[..]);
let tmp_dir_path = tmp_dir.path().join("lib");
let mut remappings = Remapping::find_many(&tmp_dir_path);
remappings.sort_unstable();
let mut expected = vec![
Remapping {
context: None,
name: "ds-auth/".to_string(),
path: to_str(tmp_dir_path.join("ds-token/lib/ds-stop/lib/ds-auth/src")),
},
Remapping {
context: None,
name: "ds-math/".to_string(),
path: to_str(tmp_dir_path.join("ds-token/lib/ds-math/src")),
},
Remapping {
context: None,
name: "ds-note/".to_string(),
path: to_str(tmp_dir_path.join("ds-token/lib/ds-stop/lib/ds-note/src")),
},
Remapping {
context: None,
name: "ds-stop/".to_string(),
path: to_str(tmp_dir_path.join("ds-token/lib/ds-stop/src")),
},
Remapping {
context: None,
name: "ds-test/".to_string(),
path: to_str(tmp_dir_path.join("ds-token/lib/ds-test/src")),
},
Remapping {
context: None,
name: "ds-token/".to_string(),
path: to_str(tmp_dir_path.join("ds-token/src")),
},
Remapping {
context: None,
name: "erc20/".to_string(),
path: to_str(tmp_dir_path.join("ds-token/lib/erc20/src")),
},
];
expected.sort_unstable();
assert_eq!(remappings, expected);
}
#[test]
fn can_resolve_nested_chainlink_remappings() {
let tmp_dir = tempdir("root").unwrap();
let paths = [
"@chainlink/contracts/src/v0.6/vendor/Contract.sol",
"@chainlink/contracts/src/v0.8/tests/Contract.sol",
"@chainlink/contracts/src/v0.7/Contract.sol",
"@chainlink/contracts/src/v0.6/Contract.sol",
"@chainlink/contracts/src/v0.5/Contract.sol",
"@chainlink/contracts/src/v0.7/tests/Contract.sol",
"@chainlink/contracts/src/v0.7/interfaces/Contract.sol",
"@chainlink/contracts/src/v0.4/tests/Contract.sol",
"@chainlink/contracts/src/v0.6/tests/Contract.sol",
"@chainlink/contracts/src/v0.5/tests/Contract.sol",
"@chainlink/contracts/src/v0.8/vendor/Contract.sol",
"@chainlink/contracts/src/v0.5/dev/Contract.sol",
"@chainlink/contracts/src/v0.6/examples/Contract.sol",
"@chainlink/contracts/src/v0.5/interfaces/Contract.sol",
"@chainlink/contracts/src/v0.4/interfaces/Contract.sol",
"@chainlink/contracts/src/v0.4/vendor/Contract.sol",
"@chainlink/contracts/src/v0.6/interfaces/Contract.sol",
"@chainlink/contracts/src/v0.7/dev/Contract.sol",
"@chainlink/contracts/src/v0.8/dev/Contract.sol",
"@chainlink/contracts/src/v0.5/vendor/Contract.sol",
"@chainlink/contracts/src/v0.7/vendor/Contract.sol",
"@chainlink/contracts/src/v0.4/Contract.sol",
"@chainlink/contracts/src/v0.8/interfaces/Contract.sol",
"@chainlink/contracts/src/v0.6/dev/Contract.sol",
];
mkdir_or_touch(tmp_dir.path(), &paths[..]);
let remappings = Remapping::find_many(tmp_dir.path());
let expected = vec![Remapping {
context: None,
name: "@chainlink/".to_string(),
path: to_str(tmp_dir.path().join("@chainlink")),
}];
assert_eq!(remappings, expected);
}
#[test]
fn can_resolve_oz_upgradeable_remappings() {
let tmp_dir = tempdir("root").unwrap();
let paths = [
"@openzeppelin/contracts-upgradeable/proxy/ERC1967/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC1155/Contract.sol",
"@openzeppelin/contracts/token/ERC777/Contract.sol",
"@openzeppelin/contracts/token/ERC721/presets/Contract.sol",
"@openzeppelin/contracts/interfaces/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC777/presets/Contract.sol",
"@openzeppelin/contracts/token/ERC1155/extensions/Contract.sol",
"@openzeppelin/contracts/proxy/Contract.sol",
"@openzeppelin/contracts/proxy/utils/Contract.sol",
"@openzeppelin/contracts-upgradeable/security/Contract.sol",
"@openzeppelin/contracts-upgradeable/utils/Contract.sol",
"@openzeppelin/contracts/token/ERC20/Contract.sol",
"@openzeppelin/contracts-upgradeable/utils/introspection/Contract.sol",
"@openzeppelin/contracts/metatx/Contract.sol",
"@openzeppelin/contracts/utils/cryptography/Contract.sol",
"@openzeppelin/contracts/token/ERC20/utils/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC20/utils/Contract.sol",
"@openzeppelin/contracts-upgradeable/proxy/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC20/presets/Contract.sol",
"@openzeppelin/contracts-upgradeable/utils/math/Contract.sol",
"@openzeppelin/contracts-upgradeable/utils/escrow/Contract.sol",
"@openzeppelin/contracts/governance/extensions/Contract.sol",
"@openzeppelin/contracts-upgradeable/interfaces/Contract.sol",
"@openzeppelin/contracts/proxy/transparent/Contract.sol",
"@openzeppelin/contracts/utils/structs/Contract.sol",
"@openzeppelin/contracts-upgradeable/access/Contract.sol",
"@openzeppelin/contracts/governance/compatibility/Contract.sol",
"@openzeppelin/contracts/governance/Contract.sol",
"@openzeppelin/contracts-upgradeable/governance/extensions/Contract.sol",
"@openzeppelin/contracts/security/Contract.sol",
"@openzeppelin/contracts-upgradeable/metatx/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC721/utils/Contract.sol",
"@openzeppelin/contracts/token/ERC721/utils/Contract.sol",
"@openzeppelin/contracts-upgradeable/governance/compatibility/Contract.sol",
"@openzeppelin/contracts/token/common/Contract.sol",
"@openzeppelin/contracts/proxy/beacon/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC721/Contract.sol",
"@openzeppelin/contracts-upgradeable/proxy/beacon/Contract.sol",
"@openzeppelin/contracts/token/ERC1155/utils/Contract.sol",
"@openzeppelin/contracts/token/ERC777/presets/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC20/Contract.sol",
"@openzeppelin/contracts-upgradeable/utils/structs/Contract.sol",
"@openzeppelin/contracts/utils/escrow/Contract.sol",
"@openzeppelin/contracts/utils/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC721/extensions/Contract.sol",
"@openzeppelin/contracts/token/ERC721/extensions/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC777/Contract.sol",
"@openzeppelin/contracts/token/ERC1155/presets/Contract.sol",
"@openzeppelin/contracts/token/ERC721/Contract.sol",
"@openzeppelin/contracts/token/ERC1155/Contract.sol",
"@openzeppelin/contracts-upgradeable/governance/Contract.sol",
"@openzeppelin/contracts/token/ERC20/extensions/Contract.sol",
"@openzeppelin/contracts-upgradeable/utils/cryptography/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC1155/presets/Contract.sol",
"@openzeppelin/contracts/access/Contract.sol",
"@openzeppelin/contracts/governance/utils/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/common/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC1155/utils/Contract.sol",
"@openzeppelin/contracts/proxy/ERC1967/Contract.sol",
"@openzeppelin/contracts/finance/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/Contract.sol",
"@openzeppelin/contracts-upgradeable/governance/utils/Contract.sol",
"@openzeppelin/contracts-upgradeable/proxy/utils/Contract.sol",
"@openzeppelin/contracts/token/ERC20/presets/Contract.sol",
"@openzeppelin/contracts/utils/math/Contract.sol",
"@openzeppelin/contracts-upgradeable/token/ERC721/presets/Contract.sol",
"@openzeppelin/contracts-upgradeable/finance/Contract.sol",
"@openzeppelin/contracts/utils/introspection/Contract.sol",
];
mkdir_or_touch(tmp_dir.path(), &paths[..]);
let remappings = Remapping::find_many(tmp_dir.path());
let expected = vec![Remapping {
context: None,
name: "@openzeppelin/".to_string(),
path: to_str(tmp_dir.path().join("@openzeppelin")),
}];
assert_eq!(remappings, expected);
}
#[test]
#[cfg_attr(windows, ignore = "Windows remappings #2347")]
fn recursive_remappings() {
let tmp_dir = tempdir("lib").unwrap();
let tmp_dir_path = tmp_dir.path();
let paths = [
"repo1/src/contract.sol",
"repo1/lib/ds-test/src/test.sol",
"repo1/lib/ds-math/src/contract.sol",
"repo1/lib/ds-math/lib/ds-test/src/test.sol",
"repo1/lib/guni-lev/src/contract.sol",
"repo1/lib/solmate/src/auth/contract.sol",
"repo1/lib/solmate/src/tokens/contract.sol",
"repo1/lib/solmate/lib/ds-test/src/test.sol",
"repo1/lib/solmate/lib/ds-test/demo/demo.sol",
"repo1/lib/openzeppelin-contracts/contracts/access/AccessControl.sol",
"repo1/lib/ds-token/lib/ds-stop/src/contract.sol",
"repo1/lib/ds-token/lib/ds-stop/lib/ds-note/src/contract.sol",
];
mkdir_or_touch(tmp_dir_path, &paths[..]);
let mut remappings = Remapping::find_many(tmp_dir_path);
remappings.sort_unstable();
let mut expected = vec![
Remapping {
context: None,
name: "repo1/".to_string(),
path: to_str(tmp_dir_path.join("repo1").join("src")),
},
Remapping {
context: None,
name: "ds-math/".to_string(),
path: to_str(tmp_dir_path.join("repo1").join("lib").join("ds-math").join("src")),
},
Remapping {
context: None,
name: "ds-test/".to_string(),
path: to_str(tmp_dir_path.join("repo1").join("lib").join("ds-test").join("src")),
},
Remapping {
context: None,
name: "guni-lev/".to_string(),
path: to_str(tmp_dir_path.join("repo1/lib/guni-lev").join("src")),
},
Remapping {
context: None,
name: "solmate/".to_string(),
path: to_str(tmp_dir_path.join("repo1/lib/solmate").join("src")),
},
Remapping {
context: None,
name: "openzeppelin-contracts/".to_string(),
path: to_str(tmp_dir_path.join("repo1/lib/openzeppelin-contracts/contracts")),
},
Remapping {
context: None,
name: "ds-stop/".to_string(),
path: to_str(tmp_dir_path.join("repo1/lib/ds-token/lib/ds-stop/src")),
},
Remapping {
context: None,
name: "ds-note/".to_string(),
path: to_str(tmp_dir_path.join("repo1/lib/ds-token/lib/ds-stop/lib/ds-note/src")),
},
];
expected.sort_unstable();
assert_eq!(remappings, expected);
}
#[test]
fn remappings() {
let tmp_dir = tempdir("tmp").unwrap();
let tmp_dir_path = tmp_dir.path().join("lib");
let repo1 = tmp_dir_path.join("src_repo");
let repo2 = tmp_dir_path.join("contracts_repo");
let dir1 = repo1.join("src");
std::fs::create_dir_all(&dir1).unwrap();
let dir2 = repo2.join("contracts");
std::fs::create_dir_all(&dir2).unwrap();
let contract1 = dir1.join("contract.sol");
touch(&contract1).unwrap();
let contract2 = dir2.join("contract.sol");
touch(&contract2).unwrap();
let mut remappings = Remapping::find_many(&tmp_dir_path);
remappings.sort_unstable();
let mut expected = vec![
Remapping {
context: None,
name: "src_repo/".to_string(),
path: format!("{}/", dir1.into_os_string().into_string().unwrap()),
},
Remapping {
context: None,
name: "contracts_repo/".to_string(),
path: format!(
"{}/",
repo2.join("contracts").into_os_string().into_string().unwrap()
),
},
];
expected.sort_unstable();
assert_eq!(remappings, expected);
}
#[test]
#[cfg_attr(windows, ignore = "Windows remappings #2347")]
fn simple_dapptools_remappings() {
let tmp_dir = tempdir("lib").unwrap();
let tmp_dir_path = tmp_dir.path();
let paths = [
"ds-test/src",
"ds-test/demo",
"ds-test/demo/demo.sol",
"ds-test/src/test.sol",
"openzeppelin/src/interfaces/c.sol",
"openzeppelin/src/token/ERC/c.sol",
"standards/src/interfaces/iweth.sol",
"uniswapv2/src",
];
mkdir_or_touch(tmp_dir_path, &paths[..]);
let mut remappings = Remapping::find_many(tmp_dir_path);
remappings.sort_unstable();
let mut expected = vec![
Remapping {
context: None,
name: "ds-test/".to_string(),
path: to_str(tmp_dir_path.join("ds-test/src")),
},
Remapping {
context: None,
name: "openzeppelin/".to_string(),
path: to_str(tmp_dir_path.join("openzeppelin/src")),
},
Remapping {
context: None,
name: "standards/".to_string(),
path: to_str(tmp_dir_path.join("standards/src")),
},
];
expected.sort_unstable();
assert_eq!(remappings, expected);
}
#[test]
#[cfg_attr(windows, ignore = "Windows remappings #2347")]
fn hardhat_remappings() {
let tmp_dir = tempdir("node_modules").unwrap();
let tmp_dir_node_modules = tmp_dir.path().join("node_modules");
let paths = [
"node_modules/@aave/aave-token/contracts/token/AaveToken.sol",
"node_modules/@aave/governance-v2/contracts/governance/Executor.sol",
"node_modules/@aave/protocol-v2/contracts/protocol/lendingpool/",
"node_modules/@aave/protocol-v2/contracts/protocol/lendingpool/LendingPool.sol",
"node_modules/@ensdomains/ens/contracts/contract.sol",
"node_modules/prettier-plugin-solidity/tests/format/ModifierDefinitions/",
"node_modules/prettier-plugin-solidity/tests/format/ModifierDefinitions/
ModifierDefinitions.sol",
"node_modules/@openzeppelin/contracts/tokens/contract.sol",
"node_modules/@openzeppelin/contracts/access/contract.sol",
"node_modules/eth-gas-reporter/mock/contracts/ConvertLib.sol",
"node_modules/eth-gas-reporter/mock/test/TestMetacoin.sol",
];
mkdir_or_touch(tmp_dir.path(), &paths[..]);
let mut remappings = Remapping::find_many(&tmp_dir_node_modules);
remappings.sort_unstable();
let mut expected = vec![
Remapping {
context: None,
name: "@aave/".to_string(),
path: to_str(tmp_dir_node_modules.join("@aave")),
},
Remapping {
context: None,
name: "@ensdomains/".to_string(),
path: to_str(tmp_dir_node_modules.join("@ensdomains")),
},
Remapping {
context: None,
name: "@openzeppelin/".to_string(),
path: to_str(tmp_dir_node_modules.join("@openzeppelin")),
},
Remapping {
context: None,
name: "eth-gas-reporter/".to_string(),
path: to_str(tmp_dir_node_modules.join("eth-gas-reporter")),
},
];
expected.sort_unstable();
assert_eq!(remappings, expected);
}
#[test]
#[cfg_attr(windows, ignore = "Windows remappings #2347")]
fn find_openzeppelin_remapping() {
let tmp_dir = tempdir("lib").unwrap();
let tmp_dir_path = tmp_dir.path();
let paths = [
"lib/ds-test/src/test.sol",
"lib/forge-std/src/test.sol",
"openzeppelin/contracts/interfaces/c.sol",
];
mkdir_or_touch(tmp_dir_path, &paths[..]);
let path = tmp_dir_path.display().to_string();
let mut remappings = Remapping::find_many(path.as_ref());
remappings.sort_unstable();
let mut expected = vec![
Remapping {
context: None,
name: "ds-test/".to_string(),
path: to_str(tmp_dir_path.join("lib/ds-test/src")),
},
Remapping {
context: None,
name: "openzeppelin/".to_string(),
path: to_str(tmp_dir_path.join("openzeppelin/contracts")),
},
Remapping {
context: None,
name: "forge-std/".to_string(),
path: to_str(tmp_dir_path.join("lib/forge-std/src")),
},
];
expected.sort_unstable();
assert_eq!(remappings, expected);
}
}