diff --git a/crates/compilers/src/resolver/parse.rs b/crates/compilers/src/resolver/parse.rs index c9b91469..e86758c6 100644 --- a/crates/compilers/src/resolver/parse.rs +++ b/crates/compilers/src/resolver/parse.rs @@ -120,6 +120,19 @@ impl SolData { let e = e.to_string(); trace!("failed parsing {file:?}: {e}"); parse_result = Err(e); + + if version.is_none() { + version = utils::capture_outer_and_inner( + content, + &utils::RE_SOL_PRAGMA_VERSION, + &["version"], + ) + .first() + .map(|(cap, name)| Spanned::new(name.as_str().to_owned(), cap.range())); + } + if !imports.is_empty() { + imports = capture_imports(content); + } } let license = content.lines().next().and_then(|line| { utils::capture_outer_and_inner( @@ -264,3 +277,111 @@ fn library_is_inlined(contract: &ast::ItemContract<'_>) -> bool { ) }) } + +/// Capture the import statement information together with aliases +pub fn capture_imports(content: &str) -> Vec> { + let mut imports = vec![]; + for cap in utils::RE_SOL_IMPORT.captures_iter(content) { + if let Some(name_match) = ["p1", "p2", "p3", "p4"].iter().find_map(|name| cap.name(name)) { + let statement_match = cap.get(0).unwrap(); + let mut aliases = vec![]; + for alias_cap in utils::RE_SOL_IMPORT_ALIAS.captures_iter(statement_match.as_str()) { + if let Some(alias) = alias_cap.name("alias") { + let alias = alias.as_str().to_owned(); + let import_alias = match alias_cap.name("target") { + Some(target) => SolImportAlias::Contract(alias, target.as_str().to_owned()), + None => SolImportAlias::File(alias), + }; + aliases.push(import_alias); + } + } + let sol_import = + SolImport::new(PathBuf::from(name_match.as_str())).set_aliases(aliases); + imports.push(Spanned::new(sol_import, statement_match.range())); + } + } + imports +} + +#[cfg(test)] +mod tests { + use super::*; + + #[track_caller] + fn assert_version(version_req: Option<&str>, src: &str) { + let data = SolData::parse(src, "test.sol".as_ref()); + assert_eq!(data.version_req, version_req.map(|v| v.parse().unwrap()), "src:\n{src}"); + } + + #[test] + fn soldata_parsing() { + assert_version(None, ""); + assert_version(None, "contract C { }"); + + // https://github.com/foundry-rs/foundry/issues/9349 + assert_version( + Some(">=0.4.22, <0.6"), + r#" +pragma solidity >=0.4.22 <0.6; + +contract BugReport { + function() external payable { + deposit(); + } + function deposit() public payable {} +} + "#, + ); + } + + #[test] + fn can_capture_curly_imports() { + let content = r#" +import { T } from "../Test.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {DsTest} from "ds-test/test.sol"; +"#; + + let captured_imports = + capture_imports(content).into_iter().map(|s| s.data.path).collect::>(); + + let expected = + utils::find_import_paths(content).map(|m| m.as_str().into()).collect::>(); + + assert_eq!(captured_imports, expected); + + assert_eq!( + captured_imports, + vec![ + PathBuf::from("../Test.sol"), + "@openzeppelin/contracts/utils/ReentrancyGuard.sol".into(), + "ds-test/test.sol".into(), + ], + ); + } + + #[test] + fn cap_capture_aliases() { + let content = r#" +import * as T from "./Test.sol"; +import { DsTest as Test } from "ds-test/test.sol"; +import "ds-test/test.sol" as Test; +import { FloatMath as Math, Math as FloatMath } from "./Math.sol"; +"#; + + let caputred_imports = + capture_imports(content).into_iter().map(|s| s.data.aliases).collect::>(); + assert_eq!( + caputred_imports, + vec![ + vec![SolImportAlias::File("T".into())], + vec![SolImportAlias::Contract("Test".into(), "DsTest".into())], + vec![SolImportAlias::File("Test".into())], + vec![ + SolImportAlias::Contract("Math".into(), "FloatMath".into()), + SolImportAlias::Contract("FloatMath".into(), "Math".into()), + ], + ] + ); + } +}