use regex::{Match, Regex};
use std::sync::LazyLock as Lazy;
pub static RE_SOL_IMPORT: Lazy<Regex> = Lazy::new(|| {
Regex::new(r#"import\s+(?:(?:"(?P<p1>.*)"|'(?P<p2>.*)')(?:\s+as\s+\w+)?|(?:(?:\w+(?:\s+as\s+\w+)?|\*\s+as\s+\w+|\{\s*(?:\w+(?:\s+as\s+\w+)?(?:\s*,\s*)?)+\s*\})\s+from\s+(?:"(?P<p3>.*)"|'(?P<p4>.*)')))\s*;"#).unwrap()
});
pub static RE_SOL_IMPORT_ALIAS: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"(?:(?P<target>\w+)|\*|'|")\s+as\s+(?P<alias>\w+)"#).unwrap());
pub static RE_SOL_PRAGMA_VERSION: Lazy<Regex> =
Lazy::new(|| Regex::new(r"pragma\s+solidity\s+(?P<version>.+?);").unwrap());
pub static RE_SOL_SDPX_LICENSE_IDENTIFIER: Lazy<Regex> =
Lazy::new(|| Regex::new(r"///?\s*SPDX-License-Identifier:\s*(?P<license>.+)").unwrap());
pub static RE_THREE_OR_MORE_NEWLINES: Lazy<Regex> = Lazy::new(|| Regex::new("\n{3,}").unwrap());
pub static RE_TWO_OR_MORE_SPACES: Lazy<Regex> = Lazy::new(|| Regex::new(" {2,}").unwrap());
pub static RE_VYPER_VERSION: Lazy<Regex> =
Lazy::new(|| Regex::new(r"#\s*(?:pragma\s+version|@version)\s+(?P<version>.+)").unwrap());
pub static RE_CONTRACT_NAMES: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"\b(?:contract|library|abstract\s+contract|interface)\s+([\w$]+)").unwrap()
});
pub fn create_contract_or_lib_name_regex(name: &str) -> Regex {
Regex::new(&format!(r#"(?:using\s+(?P<n1>{name})\s+|is\s+(?:\w+\s*,\s*)*(?P<n2>{name})(?:\s*,\s*\w+)*|(?:(?P<ignore>(?:function|error|as)\s+|\n[^\n]*(?:"([^"\n]|\\")*|'([^'\n]|\\')*))|\W+)(?P<n3>{name})(?:\.|\(| ))"#)).unwrap()
}
pub fn find_import_paths(contract: &str) -> impl Iterator<Item = Match<'_>> {
RE_SOL_IMPORT.captures_iter(contract).filter_map(|cap| {
cap.name("p1")
.or_else(|| cap.name("p2"))
.or_else(|| cap.name("p3"))
.or_else(|| cap.name("p4"))
})
}
pub fn find_version_pragma(contract: &str) -> Option<Match<'_>> {
RE_SOL_PRAGMA_VERSION.captures(contract)?.name("version")
}
pub fn capture_outer_and_inner<'a>(
content: &'a str,
regex: ®ex::Regex,
names: &[&str],
) -> Vec<(regex::Match<'a>, regex::Match<'a>)> {
regex
.captures_iter(content)
.filter_map(|cap| {
let cap_match = names.iter().find_map(|name| cap.name(name));
cap.get(0).zip(cap_match)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_find_import_paths() {
let s = r#"//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "hardhat/console.sol";
import "../contract/Contract.sol";
import { T } from "../Test.sol";
import { T } from '../Test2.sol';
"#;
assert_eq!(
vec!["hardhat/console.sol", "../contract/Contract.sol", "../Test.sol", "../Test2.sol"],
find_import_paths(s).map(|m| m.as_str()).collect::<Vec<&str>>()
);
}
#[test]
fn can_find_version() {
let s = r"//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
";
assert_eq!(Some("^0.8.0"), find_version_pragma(s).map(|s| s.as_str()));
}
#[test]
fn can_parse_curly_bracket_imports() {
let s =
r#"import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";"#;
let imports: Vec<_> = find_import_paths(s).map(|m| m.as_str()).collect();
assert_eq!(imports, vec!["@openzeppelin/contracts/utils/ReentrancyGuard.sol"])
}
#[test]
fn can_find_single_quote_imports() {
let content = r"
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
import '@openzeppelin/contracts/access/Ownable.sol';
import '@openzeppelin/contracts/utils/Address.sol';
import './../interfaces/IJBDirectory.sol';
import './../libraries/JBTokens.sol';
";
let imports: Vec<_> = find_import_paths(content).map(|m| m.as_str()).collect();
assert_eq!(
imports,
vec![
"@openzeppelin/contracts/access/Ownable.sol",
"@openzeppelin/contracts/utils/Address.sol",
"./../interfaces/IJBDirectory.sol",
"./../libraries/JBTokens.sol",
]
);
}
}