-
Notifications
You must be signed in to change notification settings - Fork 70
fix: create valid Standard JSON to verify for projects with symlinks #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
1500562
b15ac2d
409414b
e9dfe3a
4adf2ad
80acefe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -177,6 +177,52 @@ pub fn canonicalize(path: impl AsRef<Path>) -> Result<PathBuf, SolcIoError> { | |
| res.map_err(|err| SolcIoError::new(err, path)) | ||
| } | ||
|
|
||
| /// Returns a normalized Solidity file path for the given import path based on the specified | ||
| /// directory. | ||
| /// | ||
| /// This function resolves `./` and `../`, but, unlike [`canonicalize`], it does not resolve | ||
| /// symbolic links. | ||
| /// | ||
| /// The function returns an error if the normalized path does not exist in the file system. | ||
| /// | ||
| /// See also: <https://docs.soliditylang.org/en/v0.8.23/path-resolution.html> | ||
| pub fn normalize_solidity_import_path( | ||
| directory: impl AsRef<Path>, | ||
| import_path: impl AsRef<Path>, | ||
| ) -> Result<PathBuf, SolcIoError> { | ||
| let original = directory.as_ref().join(import_path); | ||
| let cleaned = clean_solidity_path(&original); | ||
|
|
||
| // this is to align the behavior with `canonicalize` | ||
| use path_slash::PathExt; | ||
| let normalized = PathBuf::from(dunce::simplified(&cleaned).to_slash_lossy().as_ref()); | ||
|
|
||
| fs::metadata(&normalized).map(|_| normalized).map_err(|err| SolcIoError::new(err, original)) | ||
| } | ||
|
|
||
| fn clean_solidity_path(original_path: impl AsRef<Path>) -> PathBuf { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rust doesn't have this normalization function (resolves
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gotcha—I'll defer to @mattsse here, but the function is so small I don't mind keeping it here instead of pulling in an additional dep, as we also have tests.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we add some docs here? I know it's an internal function, but having docs is nice and useful to revisit this later whenever needed |
||
| let mut new_path = Vec::new(); | ||
|
|
||
| for component in original_path.as_ref().components() { | ||
| match component { | ||
| Component::Prefix(..) | Component::RootDir | Component::Normal(..) => { | ||
| new_path.push(component); | ||
| } | ||
| Component::CurDir => {} | ||
| Component::ParentDir => match new_path.last() { | ||
| Some(Component::Normal(..)) => { | ||
| new_path.pop(); | ||
| } | ||
| _ => { | ||
| new_path.push(component); | ||
| } | ||
|
||
| }, | ||
| } | ||
| } | ||
|
|
||
| new_path.iter().collect() | ||
| } | ||
|
|
||
| /// Returns the same path config but with canonicalized paths. | ||
| /// | ||
| /// This will take care of potential symbolic linked directories. | ||
|
|
@@ -228,7 +274,7 @@ pub fn resolve_library(libs: &[impl AsRef<Path>], source: impl AsRef<Path>) -> O | |
| /// until the `root` is reached. | ||
| /// | ||
| /// If an existing file under `root` is found, this returns the path up to the `import` path and the | ||
| /// canonicalized `import` path itself: | ||
| /// normalized `import` path itself: | ||
| /// | ||
| /// For example for following layout: | ||
| /// | ||
|
|
@@ -247,7 +293,7 @@ pub fn resolve_absolute_library( | |
| ) -> Option<(PathBuf, PathBuf)> { | ||
| let mut parent = cwd.parent()?; | ||
| while parent != root { | ||
| if let Ok(import) = canonicalize(parent.join(import)) { | ||
| if let Ok(import) = normalize_solidity_import_path(parent, import) { | ||
| return Some((parent.to_path_buf(), import)); | ||
| } | ||
| parent = parent.parent()?; | ||
|
|
@@ -654,6 +700,98 @@ pragma solidity ^0.8.0; | |
| assert_eq!(Some("^0.8.0"), find_version_pragma(s).map(|s| s.as_str())); | ||
| } | ||
|
|
||
| #[test] | ||
| fn can_normalize_solidity_import_path() { | ||
| let dir = tempfile::tempdir().unwrap(); | ||
| let dir_path = dir.path(); | ||
|
|
||
| // File structure: | ||
| // | ||
| // `dir_path` | ||
| // └── src (`cwd`) | ||
| // ├── Token.sol | ||
| // └── common | ||
| // └── Burnable.sol | ||
|
|
||
| fs::create_dir_all(dir_path.join("src/common")).unwrap(); | ||
| fs::write(dir_path.join("src/Token.sol"), "").unwrap(); | ||
| fs::write(dir_path.join("src/common/Burnable.sol"), "").unwrap(); | ||
|
|
||
| // assume that the import path is specified in Token.sol | ||
| let cwd = dir_path.join("src"); | ||
|
|
||
| assert_eq!( | ||
| normalize_solidity_import_path(&cwd, "./common/Burnable.sol").unwrap(), | ||
| dir_path.join("src/common/Burnable.sol"), | ||
| ); | ||
|
|
||
| assert!(normalize_solidity_import_path(&cwd, "./common/Pausable.sol").is_err()); | ||
| } | ||
|
|
||
| // This test is exclusive to unix because creating a symlink is a privileged action on Windows. | ||
| // https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_dir.html#limitations | ||
| #[test] | ||
| #[cfg(unix)] | ||
| fn can_normalize_solidity_import_path_symlink() { | ||
| let dir = tempfile::tempdir().unwrap(); | ||
| let dir_path = dir.path(); | ||
|
|
||
| // File structure: | ||
| // | ||
| // `dir_path` | ||
| // ├── dependency | ||
| // │ └── Math.sol | ||
| // └── project | ||
| // ├── node_modules | ||
| // │ └── dependency -> symlink to actual 'dependency' directory | ||
| // └── src (`cwd`) | ||
| // └── Token.sol | ||
|
|
||
| fs::create_dir_all(dir_path.join("project/src")).unwrap(); | ||
| fs::write(dir_path.join("project/src/Token.sol"), "").unwrap(); | ||
| fs::create_dir(dir_path.join("project/node_modules")).unwrap(); | ||
|
|
||
| fs::create_dir(dir_path.join("dependency")).unwrap(); | ||
| fs::write(dir_path.join("dependency/Math.sol"), "").unwrap(); | ||
|
|
||
| std::os::unix::fs::symlink( | ||
| dir_path.join("dependency"), | ||
| dir_path.join("project/node_modules/dependency"), | ||
| ) | ||
| .unwrap(); | ||
|
|
||
| // assume that the import path is specified in Token.sol | ||
| let cwd = dir_path.join("project/src"); | ||
|
|
||
| assert_eq!( | ||
| normalize_solidity_import_path(cwd, "../node_modules/dependency/Math.sol").unwrap(), | ||
| dir_path.join("project/node_modules/dependency/Math.sol"), | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn can_clean_solidity_path() { | ||
| assert_eq!(clean_solidity_path("a"), PathBuf::from("a")); | ||
| assert_eq!(clean_solidity_path("./a"), PathBuf::from("a")); | ||
| assert_eq!(clean_solidity_path("../a"), PathBuf::from("../a")); | ||
| assert_eq!(clean_solidity_path("/a/"), PathBuf::from("/a")); | ||
| assert_eq!(clean_solidity_path("//a"), PathBuf::from("/a")); | ||
| assert_eq!(clean_solidity_path("a/b"), PathBuf::from("a/b")); | ||
| assert_eq!(clean_solidity_path("a//b"), PathBuf::from("a/b")); | ||
| assert_eq!(clean_solidity_path("/a/b"), PathBuf::from("/a/b")); | ||
| assert_eq!(clean_solidity_path("a/./b"), PathBuf::from("a/b")); | ||
| assert_eq!(clean_solidity_path("a/././b"), PathBuf::from("a/b")); | ||
| assert_eq!(clean_solidity_path("/a/../b"), PathBuf::from("/b")); | ||
| assert_eq!(clean_solidity_path("a/b/c"), PathBuf::from("a/b/c")); | ||
| assert_eq!(clean_solidity_path("a/b/../c"), PathBuf::from("a/c")); | ||
| assert_eq!(clean_solidity_path("a/b/../../c"), PathBuf::from("c")); | ||
| assert_eq!(clean_solidity_path("a/b/../../../c"), PathBuf::from("../c")); | ||
| assert_eq!( | ||
| clean_solidity_path("a/../b/../../c/./Token.sol"), | ||
| PathBuf::from("../c/Token.sol") | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn can_find_ancestor() { | ||
| let a = Path::new("/foo/bar/bar/test.txt"); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's this call for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function
normalize_solidity_import_pathchecks if the returned path exists in the file system. The use offs::metadata(&normalized)is to obtain anio::Errorfor non-existing files without reading their entire contents. To return the same type of error asutils::canonicalize, anio::Erroris necessary forSolcIoError.While there is a specific function,
Path::is_file(), that confirms if a path exists in the file system, it only returns a bool.https://doc.rust-lang.org/std/path/struct.Path.html#method.is_file
However, I have now realized that there's an alias
Path::metadata()for paths. Therefore, I will switch to using this function and add a comment.https://doc.rust-lang.org/std/path/struct.Path.html#method.metadata