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
|
/// Filesystem IO error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io(#[from] SolcIoError),
|
Io(#[from] SolcIoError),
|
||||||
|
#[error("File could not be resolved due to broken symlink: {0}.")]
|
||||||
|
ResolveBadSymlink(SolcIoError),
|
||||||
/// Failed to resolve a file
|
/// Failed to resolve a file
|
||||||
#[error("Failed to resolve file: {0}.\n Check configured remappings.")]
|
#[error("Failed to resolve file: {0}.\n Check configured remappings.")]
|
||||||
Resolve(SolcIoError),
|
Resolve(SolcIoError),
|
||||||
#[error("File could not be resolved due to broken symlink: {0}.")]
|
#[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.")]
|
||||||
ResolveBadSymlink(SolcIoError),
|
ResolveCaseSensitiveFileName { error: SolcIoError, existing_file: PathBuf },
|
||||||
#[error(
|
#[error(
|
||||||
r#"Failed to resolve file: {0}.
|
r#"{0}.
|
||||||
--> {1:?}
|
--> {1:?}
|
||||||
{2:?}
|
{2:?}"#
|
||||||
Check configured remappings."#
|
|
||||||
)]
|
)]
|
||||||
FailedResolveImport(SolcIoError, PathBuf, PathBuf),
|
FailedResolveImport(Box<SolcError>, PathBuf, PathBuf),
|
||||||
#[cfg(feature = "svm-solc")]
|
#[cfg(feature = "svm-solc")]
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SvmError(#[from] svm::SolcVmError),
|
SvmError(#[from] svm::SolcVmError),
|
||||||
|
|
|
@ -59,6 +59,7 @@ use std::{
|
||||||
mod parse;
|
mod parse;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
||||||
|
use crate::utils::find_case_sensitive_existing_file;
|
||||||
pub use parse::SolImportAlias;
|
pub use parse::SolImportAlias;
|
||||||
pub use tree::{print, Charset, TreeOptions};
|
pub use tree::{print, Charset, TreeOptions};
|
||||||
|
|
||||||
|
@ -371,10 +372,12 @@ impl Graph {
|
||||||
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)
|
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
match err {
|
match err {
|
||||||
SolcError::Resolve(err) => {
|
err @ SolcError::ResolveCaseSensitiveFileName { .. } |
|
||||||
// make the error more verbose
|
err @ SolcError::Resolve(_) => {
|
||||||
|
// make the error more helpful by providing additional
|
||||||
|
// context
|
||||||
SolcError::FailedResolveImport(
|
SolcError::FailedResolveImport(
|
||||||
err,
|
Box::new(err),
|
||||||
node.path.clone(),
|
node.path.clone(),
|
||||||
import_path.clone(),
|
import_path.clone(),
|
||||||
)
|
)
|
||||||
|
@ -832,11 +835,22 @@ impl Node {
|
||||||
pub fn read(file: impl AsRef<Path>) -> Result<Self> {
|
pub fn read(file: impl AsRef<Path>) -> Result<Self> {
|
||||||
let file = file.as_ref();
|
let file = file.as_ref();
|
||||||
let source = Source::read(file).map_err(|err| {
|
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)
|
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 {
|
} else {
|
||||||
SolcError::Resolve(err)
|
SolcError::Resolve(err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
SolcError::Resolve(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
let data = SolData::parse(source.as_ref(), file);
|
let data = SolData::parse(source.as_ref(), file);
|
||||||
Ok(Self { path: file.to_path_buf(), source, data })
|
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
|
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"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use tokio::runtime::{Handle, Runtime};
|
use tokio::runtime::{Handle, Runtime};
|
||||||
|
|
||||||
|
@ -453,6 +473,7 @@ pub fn create_parent_dir_all(file: impl AsRef<Path>) -> Result<(), SolcError> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::resolver::Node;
|
||||||
use solang_parser::pt::SourceUnitPart;
|
use solang_parser::pt::SourceUnitPart;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
|
@ -460,6 +481,45 @@ mod tests {
|
||||||
};
|
};
|
||||||
use tempdir;
|
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]
|
#[test]
|
||||||
fn can_create_parent_dirs_with_ext() {
|
fn can_create_parent_dirs_with_ext() {
|
||||||
let tmp_dir = tempdir("out").unwrap();
|
let tmp_dir = tempdir("out").unwrap();
|
||||||
|
|
Loading…
Reference in New Issue