feat(solc): improve error on case mismatch (#1998)
This commit is contained in:
parent
6ac3e75c6a
commit
d1df3417f7
|
@ -27,18 +27,19 @@ pub enum SolcError {
|
|||
/// Filesystem IO error
|
||||
#[error(transparent)]
|
||||
Io(#[from] SolcIoError),
|
||||
#[error("File could not be resolved due to broken symlink: {0}.")]
|
||||
ResolveBadSymlink(SolcIoError),
|
||||
/// Failed to resolve a file
|
||||
#[error("Failed to resolve file: {0}.\n Check configured remappings.")]
|
||||
Resolve(SolcIoError),
|
||||
#[error("File could not be resolved due to broken symlink: {0}.")]
|
||||
ResolveBadSymlink(SolcIoError),
|
||||
#[error("File cannot be resolved due to mismatch of file name case: {error}.\n Found existing file: {existing_file:?}\n Please check the case of the import.")]
|
||||
ResolveCaseSensitiveFileName { error: SolcIoError, existing_file: PathBuf },
|
||||
#[error(
|
||||
r#"Failed to resolve file: {0}.
|
||||
r#"{0}.
|
||||
--> {1:?}
|
||||
{2:?}
|
||||
Check configured remappings."#
|
||||
{2:?}"#
|
||||
)]
|
||||
FailedResolveImport(SolcIoError, PathBuf, PathBuf),
|
||||
FailedResolveImport(Box<SolcError>, PathBuf, PathBuf),
|
||||
#[cfg(feature = "svm-solc")]
|
||||
#[error(transparent)]
|
||||
SvmError(#[from] svm::SolcVmError),
|
||||
|
|
|
@ -59,6 +59,7 @@ use std::{
|
|||
mod parse;
|
||||
mod tree;
|
||||
|
||||
use crate::utils::find_case_sensitive_existing_file;
|
||||
pub use parse::SolImportAlias;
|
||||
pub use tree::{print, Charset, TreeOptions};
|
||||
|
||||
|
@ -371,10 +372,12 @@ impl Graph {
|
|||
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)
|
||||
.map_err(|err| {
|
||||
match err {
|
||||
SolcError::Resolve(err) => {
|
||||
// make the error more verbose
|
||||
err @ SolcError::ResolveCaseSensitiveFileName { .. } |
|
||||
err @ SolcError::Resolve(_) => {
|
||||
// make the error more helpful by providing additional
|
||||
// context
|
||||
SolcError::FailedResolveImport(
|
||||
err,
|
||||
Box::new(err),
|
||||
node.path.clone(),
|
||||
import_path.clone(),
|
||||
)
|
||||
|
@ -832,11 +835,22 @@ impl Node {
|
|||
pub fn read(file: impl AsRef<Path>) -> Result<Self> {
|
||||
let file = file.as_ref();
|
||||
let source = Source::read(file).map_err(|err| {
|
||||
if !err.path().exists() && err.path().is_symlink() {
|
||||
let exists = err.path().exists();
|
||||
if !exists && err.path().is_symlink() {
|
||||
SolcError::ResolveBadSymlink(err)
|
||||
} else {
|
||||
// This is an additional check useful on OS that have case-sensitive paths, See also <https://docs.soliditylang.org/en/v0.8.17/path-resolution.html#import-callback>
|
||||
if !exists {
|
||||
// check if there exists a file with different case
|
||||
if let Some(existing_file) = find_case_sensitive_existing_file(file) {
|
||||
SolcError::ResolveCaseSensitiveFileName { error: err, existing_file }
|
||||
} else {
|
||||
SolcError::Resolve(err)
|
||||
}
|
||||
} else {
|
||||
SolcError::Resolve(err)
|
||||
}
|
||||
}
|
||||
})?;
|
||||
let data = SolData::parse(source.as_ref(), file);
|
||||
Ok(Self { path: file.to_path_buf(), source, data })
|
||||
|
|
|
@ -385,6 +385,26 @@ pub(crate) fn find_fave_or_alt_path(root: impl AsRef<Path>, fave: &str, alt: &st
|
|||
p
|
||||
}
|
||||
|
||||
/// Attempts to find a file with different case that exists next to the `non_existing` file
|
||||
pub(crate) fn find_case_sensitive_existing_file(non_existing: &Path) -> Option<PathBuf> {
|
||||
let non_existing_file_name = non_existing.file_name()?;
|
||||
let parent = non_existing.parent()?;
|
||||
WalkDir::new(parent)
|
||||
.max_depth(1)
|
||||
.into_iter()
|
||||
.filter_map(Result::ok)
|
||||
.filter(|e| e.file_type().is_file())
|
||||
.find_map(|e| {
|
||||
let existing_file_name = e.path().file_name()?;
|
||||
if existing_file_name.eq_ignore_ascii_case(non_existing_file_name) &&
|
||||
existing_file_name != non_existing_file_name
|
||||
{
|
||||
return Some(e.path().to_path_buf())
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::runtime::{Handle, Runtime};
|
||||
|
||||
|
@ -453,6 +473,7 @@ pub fn create_parent_dir_all(file: impl AsRef<Path>) -> Result<(), SolcError> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::resolver::Node;
|
||||
use solang_parser::pt::SourceUnitPart;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
|
@ -460,6 +481,45 @@ mod tests {
|
|||
};
|
||||
use tempdir;
|
||||
|
||||
#[test]
|
||||
fn can_find_different_case() {
|
||||
let tmp_dir = tempdir("out").unwrap();
|
||||
let path = tmp_dir.path().join("forge-std");
|
||||
create_dir_all(&path).unwrap();
|
||||
let existing = path.join("Test.sol");
|
||||
let non_existing = path.join("test.sol");
|
||||
std::fs::write(&existing, b"").unwrap();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
assert!(!non_existing.exists());
|
||||
|
||||
let found = find_case_sensitive_existing_file(&non_existing).unwrap();
|
||||
assert_eq!(found, existing);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn can_read_different_case() {
|
||||
let tmp_dir = tempdir("out").unwrap();
|
||||
let path = tmp_dir.path().join("forge-std");
|
||||
create_dir_all(&path).unwrap();
|
||||
let existing = path.join("Test.sol");
|
||||
let non_existing = path.join("test.sol");
|
||||
std::fs::write(
|
||||
&existing,
|
||||
r#"
|
||||
pragma solidity ^0.8.10;
|
||||
contract A {}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(!non_existing.exists());
|
||||
|
||||
let found = Node::read(&non_existing).unwrap_err();
|
||||
matches!(found, SolcError::ResolveCaseSensitiveFileName { .. });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_create_parent_dirs_with_ext() {
|
||||
let tmp_dir = tempdir("out").unwrap();
|
||||
|
|
Loading…
Reference in New Issue