Skip to content

Commit f6e4e6a

Browse files
authored
fix: allow single sol file remappings (#295)
ref foundry-rs/foundry#6706 tested and confirm solves foundry-rs/foundry#8499 too - do not add trailing `/` to remappings if single .sol file remapped - foundry-rs/foundry#6706 (comment) - if `stripped_import` is empty then do not join when resolving library import as will append `/` - foundry-rs/foundry#6706 (comment) - fix display so `forge remappings` properly display remappings `@ens/utils/BytesUtils.sol=src/BytesUtils.sol`
1 parent 910af38 commit f6e4e6a

File tree

2 files changed

+76
-7
lines changed

2 files changed

+76
-7
lines changed

crates/artifacts/solc/src/remappings/mod.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,11 @@ impl fmt::Display for Remapping {
138138
}
139139
s.push(':');
140140
}
141-
let name =
142-
if !self.name.ends_with('/') { format!("{}/", self.name) } else { self.name.clone() };
141+
let name = if needs_trailing_slash(&self.name) {
142+
format!("{}/", self.name)
143+
} else {
144+
self.name.clone()
145+
};
143146
s.push_str(&{
144147
#[cfg(target_os = "windows")]
145148
{
@@ -153,7 +156,7 @@ impl fmt::Display for Remapping {
153156
}
154157
});
155158

156-
if !s.ends_with('/') {
159+
if needs_trailing_slash(&s) {
157160
s.push('/');
158161
}
159162
f.write_str(&s)
@@ -241,7 +244,7 @@ impl fmt::Display for RelativeRemapping {
241244
}
242245
});
243246

244-
if !s.ends_with('/') {
247+
if needs_trailing_slash(&s) {
245248
s.push('/');
246249
}
247250
f.write_str(&s)
@@ -252,10 +255,10 @@ impl From<RelativeRemapping> for Remapping {
252255
fn from(r: RelativeRemapping) -> Self {
253256
let RelativeRemapping { context, mut name, path } = r;
254257
let mut path = path.relative().display().to_string();
255-
if !path.ends_with('/') {
258+
if needs_trailing_slash(&path) {
256259
path.push('/');
257260
}
258-
if !name.ends_with('/') {
261+
if needs_trailing_slash(&name) {
259262
name.push('/');
260263
}
261264
Self { context, name, path }
@@ -341,6 +344,15 @@ impl<'de> Deserialize<'de> for RelativeRemapping {
341344
}
342345
}
343346

347+
/// Helper to determine if name or path of a remapping needs trailing slash.
348+
/// Returns false if it already ends with a slash or if remapping is a solidity file.
349+
/// Used to preserve name and path of single file remapping, see
350+
/// <https://github.com/foundry-rs/foundry/issues/6706>
351+
/// <https://github.com/foundry-rs/foundry/issues/8499>
352+
fn needs_trailing_slash(name_or_path: &str) -> bool {
353+
!name_or_path.ends_with('/') && !name_or_path.ends_with(".sol")
354+
}
355+
344356
#[cfg(test)]
345357
mod tests {
346358
pub use super::*;
@@ -423,4 +435,21 @@ mod tests {
423435
);
424436
assert_eq!(remapping.to_string(), "oz/=a/b/c/d/".to_string());
425437
}
438+
439+
// <https://github.com/foundry-rs/foundry/issues/6706#issuecomment-3141270852>
440+
#[test]
441+
fn can_preserve_single_sol_file_remapping() {
442+
let remapping = "@my-lib/B.sol=lib/my-lib/B.sol";
443+
let remapping = Remapping::from_str(remapping).unwrap();
444+
445+
assert_eq!(
446+
remapping,
447+
Remapping {
448+
context: None,
449+
name: "@my-lib/B.sol".to_string(),
450+
path: "lib/my-lib/B.sol".to_string()
451+
}
452+
);
453+
assert_eq!(remapping.to_string(), "@my-lib/B.sol=lib/my-lib/B.sol".to_string());
454+
}
426455
}

crates/compilers/src/config.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,12 @@ impl<L> ProjectPathsConfig<L> {
524524
})
525525
.find_map(|r| {
526526
import.strip_prefix(&r.name).ok().map(|stripped_import| {
527-
let lib_path = Path::new(&r.path).join(stripped_import);
527+
let lib_path =
528+
if stripped_import.as_os_str().is_empty() && r.path.ends_with(".sol") {
529+
r.path.clone().into()
530+
} else {
531+
Path::new(&r.path).join(stripped_import)
532+
};
528533

529534
// we handle the edge case where the path of a remapping ends with "contracts"
530535
// (`<name>/=.../contracts`) and the stripped import also starts with
@@ -1196,4 +1201,39 @@ mod tests {
11961201
dependency.join("A.sol")
11971202
);
11981203
}
1204+
1205+
#[test]
1206+
fn can_resolve_single_file_mapped_import() {
1207+
let dir = tempfile::tempdir().unwrap();
1208+
let mut config = ProjectPathsConfig::builder().root(dir.path()).build::<()>().unwrap();
1209+
config.create_all().unwrap();
1210+
1211+
fs::write(
1212+
config.sources.join("A.sol"),
1213+
r#"pragma solidity ^0.8.0; import "@my-lib/B.sol"; contract A is B {}"#,
1214+
)
1215+
.unwrap();
1216+
1217+
let dependency = config.root.join("my-lib");
1218+
fs::create_dir(&dependency).unwrap();
1219+
fs::write(dependency.join("B.sol"), r"pragma solidity ^0.8.0; contract B {}").unwrap();
1220+
1221+
config.remappings.push(Remapping {
1222+
context: None,
1223+
name: "@my-lib/B.sol".into(),
1224+
path: "my-lib/B.sol".into(),
1225+
});
1226+
1227+
// Test that single file import / remapping resolves to file.
1228+
assert!(config
1229+
.resolve_import_and_include_paths(
1230+
&config.sources,
1231+
Path::new("@my-lib/B.sol"),
1232+
&mut Default::default(),
1233+
)
1234+
.unwrap()
1235+
.to_str()
1236+
.unwrap()
1237+
.ends_with("my-lib/B.sol"));
1238+
}
11991239
}

0 commit comments

Comments
 (0)