fix(solc): flatten import aliases (#1192)
* import aliases * fix solang alias parsing * fix token replacement * minor * remove log * remove log * address pr comments * rollback
This commit is contained in:
parent
b34c034bc4
commit
a656830790
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
cache::SOLIDITY_FILES_CACHE_FILENAME,
|
cache::SOLIDITY_FILES_CACHE_FILENAME,
|
||||||
error::{Result, SolcError, SolcIoError},
|
error::{Result, SolcError, SolcIoError},
|
||||||
remappings::Remapping,
|
remappings::Remapping,
|
||||||
resolver::Graph,
|
resolver::{Graph, SolImportAlias},
|
||||||
utils, Source, Sources,
|
utils, Source, Sources,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -281,43 +281,69 @@ impl ProjectPathsConfig {
|
||||||
let target_node = graph.node(*target_index);
|
let target_node = graph.node(*target_index);
|
||||||
|
|
||||||
let mut imports = target_node.imports().clone();
|
let mut imports = target_node.imports().clone();
|
||||||
imports.sort_by_key(|x| x.loc().0);
|
imports.sort_by_key(|x| x.loc().start);
|
||||||
|
|
||||||
let mut content = target_node.content().as_bytes().to_vec();
|
let mut content = target_node.content().to_owned();
|
||||||
|
|
||||||
|
for alias in imports.iter().flat_map(|i| i.data().aliases()) {
|
||||||
|
let (alias, target) = match alias {
|
||||||
|
SolImportAlias::Contract(alias, target) => (alias.clone(), target.clone()),
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
let name_regex = utils::create_contract_or_lib_name_regex(&alias);
|
||||||
|
let target_len = target.len() as isize;
|
||||||
|
let mut replace_offset = 0;
|
||||||
|
for cap in name_regex.captures_iter(&content.clone()) {
|
||||||
|
if cap.name("ignore").is_some() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if let Some(name_match) =
|
||||||
|
vec!["n1", "n2", "n3"].iter().find_map(|name| cap.name(name))
|
||||||
|
{
|
||||||
|
let name_match_range =
|
||||||
|
utils::range_by_offset(&name_match.range(), replace_offset);
|
||||||
|
replace_offset += target_len - (name_match_range.len() as isize);
|
||||||
|
content.replace_range(name_match_range, &target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut content = content.as_bytes().to_vec();
|
||||||
let mut offset = 0_isize;
|
let mut offset = 0_isize;
|
||||||
|
|
||||||
if strip_license {
|
if strip_license {
|
||||||
if let Some(license) = target_node.license() {
|
if let Some(license) = target_node.license() {
|
||||||
let (start, end) = license.loc_by_offset(offset);
|
let license_range = license.loc_by_offset(offset);
|
||||||
content.splice(start..end, std::iter::empty());
|
offset -= license_range.len() as isize;
|
||||||
offset -= (end - start) as isize;
|
content.splice(license_range, std::iter::empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strip_version_pragma {
|
if strip_version_pragma {
|
||||||
if let Some(version) = target_node.version() {
|
if let Some(version) = target_node.version() {
|
||||||
let (start, end) = version.loc_by_offset(offset);
|
let version_range = version.loc_by_offset(offset);
|
||||||
content.splice(start..end, std::iter::empty());
|
offset -= version_range.len() as isize;
|
||||||
offset -= (end - start) as isize;
|
content.splice(version_range, std::iter::empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strip_experimental_pragma {
|
if strip_experimental_pragma {
|
||||||
if let Some(experiment) = target_node.experimental() {
|
if let Some(experiment) = target_node.experimental() {
|
||||||
let (start, end) = experiment.loc_by_offset(offset);
|
let experimental_pragma_range = experiment.loc_by_offset(offset);
|
||||||
content.splice(start..end, std::iter::empty());
|
offset -= experimental_pragma_range.len() as isize;
|
||||||
offset -= (end - start) as isize;
|
content.splice(experimental_pragma_range, std::iter::empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for import in imports.iter() {
|
for import in imports.iter() {
|
||||||
let import_path = self.resolve_import(target_dir, import.data())?;
|
let import_path = self.resolve_import(target_dir, import.data().path())?;
|
||||||
let s = self.flatten_node(&import_path, graph, imported, true, true, true)?;
|
let s = self.flatten_node(&import_path, graph, imported, true, true, true)?;
|
||||||
|
|
||||||
let import_content = s.as_bytes();
|
let import_content = s.as_bytes();
|
||||||
let import_content_len = import_content.len() as isize;
|
let import_content_len = import_content.len() as isize;
|
||||||
let (start, end) = import.loc_by_offset(offset);
|
let import_range = import.loc_by_offset(offset);
|
||||||
content.splice(start..end, import_content.iter().copied());
|
offset += import_content_len - (import_range.len() as isize);
|
||||||
offset += import_content_len - ((end - start) as isize);
|
content.splice(import_range, import_content.iter().copied());
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = String::from_utf8(content).map_err(|err| {
|
let result = String::from_utf8(content).map_err(|err| {
|
||||||
|
|
|
@ -52,7 +52,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use parse::{SolData, SolDataUnit};
|
use parse::{SolData, SolDataUnit, SolImport};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
use semver::VersionReq;
|
use semver::VersionReq;
|
||||||
|
@ -62,6 +62,7 @@ use crate::{error::Result, utils, ProjectPathsConfig, SolcError, Source, Sources
|
||||||
mod parse;
|
mod parse;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
||||||
|
pub use parse::SolImportAlias;
|
||||||
pub use tree::{print, Charset, TreeOptions};
|
pub use tree::{print, Charset, TreeOptions};
|
||||||
|
|
||||||
/// The underlying edges of the graph which only contains the raw relationship data.
|
/// The underlying edges of the graph which only contains the raw relationship data.
|
||||||
|
@ -339,13 +340,14 @@ impl Graph {
|
||||||
};
|
};
|
||||||
|
|
||||||
for import in node.data.imports.iter() {
|
for import in node.data.imports.iter() {
|
||||||
match paths.resolve_import(cwd, import.data()) {
|
let import_path = import.data().path();
|
||||||
|
match paths.resolve_import(cwd, import_path) {
|
||||||
Ok(import) => {
|
Ok(import) => {
|
||||||
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)?;
|
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)?;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if unresolved_paths.insert(import.data().to_path_buf()) {
|
if unresolved_paths.insert(import_path.to_path_buf()) {
|
||||||
crate::report::unresolved_import(import.data(), &paths.remappings);
|
crate::report::unresolved_import(import_path, &paths.remappings);
|
||||||
}
|
}
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"failed to resolve import component \"{:?}\" for {:?}",
|
"failed to resolve import component \"{:?}\" for {:?}",
|
||||||
|
@ -772,7 +774,7 @@ impl Node {
|
||||||
&self.source.content
|
&self.source.content
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn imports(&self) -> &Vec<SolDataUnit<PathBuf>> {
|
pub fn imports(&self) -> &Vec<SolDataUnit<SolImport>> {
|
||||||
&self.data.imports
|
&self.data.imports
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -854,7 +856,7 @@ mod tests {
|
||||||
let dapp_test = graph.node(1);
|
let dapp_test = graph.node(1);
|
||||||
assert_eq!(dapp_test.path, paths.sources.join("Dapp.t.sol"));
|
assert_eq!(dapp_test.path, paths.sources.join("Dapp.t.sol"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
dapp_test.data.imports.iter().map(|i| i.data()).collect::<Vec<&PathBuf>>(),
|
dapp_test.data.imports.iter().map(|i| i.data().path()).collect::<Vec<&PathBuf>>(),
|
||||||
vec![&PathBuf::from("ds-test/test.sol"), &PathBuf::from("./Dapp.sol")]
|
vec![&PathBuf::from("ds-test/test.sol"), &PathBuf::from("./Dapp.sol")]
|
||||||
);
|
);
|
||||||
assert_eq!(graph.imported_nodes(1).to_vec(), vec![2, 0]);
|
assert_eq!(graph.imported_nodes(1).to_vec(), vec![2, 0]);
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use crate::{utils, Solc};
|
use crate::{utils, Solc};
|
||||||
use regex::Match;
|
|
||||||
use semver::VersionReq;
|
use semver::VersionReq;
|
||||||
use solang_parser::pt::{
|
use solang_parser::pt::{
|
||||||
ContractPart, ContractTy, FunctionAttribute, FunctionDefinition, Import, Loc, SourceUnitPart,
|
ContractPart, ContractTy, FunctionAttribute, FunctionDefinition, Import, Loc, SourceUnitPart,
|
||||||
Visibility,
|
Visibility,
|
||||||
};
|
};
|
||||||
use std::path::{Path, PathBuf};
|
use std::{
|
||||||
|
ops::Range,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
/// Represents various information about a solidity file parsed via [solang_parser]
|
/// Represents various information about a solidity file parsed via [solang_parser]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -14,7 +16,7 @@ pub struct SolData {
|
||||||
pub license: Option<SolDataUnit<String>>,
|
pub license: Option<SolDataUnit<String>>,
|
||||||
pub version: Option<SolDataUnit<String>>,
|
pub version: Option<SolDataUnit<String>>,
|
||||||
pub experimental: Option<SolDataUnit<String>>,
|
pub experimental: Option<SolDataUnit<String>>,
|
||||||
pub imports: Vec<SolDataUnit<PathBuf>>,
|
pub imports: Vec<SolDataUnit<SolImport>>,
|
||||||
pub version_req: Option<VersionReq>,
|
pub version_req: Option<VersionReq>,
|
||||||
pub libraries: Vec<SolLibrary>,
|
pub libraries: Vec<SolLibrary>,
|
||||||
pub contracts: Vec<SolContract>,
|
pub contracts: Vec<SolContract>,
|
||||||
|
@ -39,7 +41,7 @@ impl SolData {
|
||||||
pub fn parse(content: &str, file: &Path) -> Self {
|
pub fn parse(content: &str, file: &Path) -> Self {
|
||||||
let mut version = None;
|
let mut version = None;
|
||||||
let mut experimental = None;
|
let mut experimental = None;
|
||||||
let mut imports = Vec::<SolDataUnit<PathBuf>>::new();
|
let mut imports = Vec::<SolDataUnit<SolImport>>::new();
|
||||||
let mut libraries = Vec::new();
|
let mut libraries = Vec::new();
|
||||||
let mut contracts = Vec::new();
|
let mut contracts = Vec::new();
|
||||||
|
|
||||||
|
@ -50,22 +52,30 @@ impl SolData {
|
||||||
SourceUnitPart::PragmaDirective(loc, _, pragma, value) => {
|
SourceUnitPart::PragmaDirective(loc, _, pragma, value) => {
|
||||||
if pragma.name == "solidity" {
|
if pragma.name == "solidity" {
|
||||||
// we're only interested in the solidity version pragma
|
// we're only interested in the solidity version pragma
|
||||||
version = Some(SolDataUnit::new(value.string.clone(), loc.into()));
|
version = Some(SolDataUnit::from_loc(value.string.clone(), loc));
|
||||||
}
|
}
|
||||||
|
|
||||||
if pragma.name == "experimental" {
|
if pragma.name == "experimental" {
|
||||||
experimental =
|
experimental =
|
||||||
Some(SolDataUnit::new(value.string.clone(), loc.into()));
|
Some(SolDataUnit::from_loc(value.string.clone(), loc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SourceUnitPart::ImportDirective(_, import) => {
|
SourceUnitPart::ImportDirective(_, import) => {
|
||||||
let (import, loc) = match import {
|
let (import, ids, loc) = match import {
|
||||||
Import::Plain(s, l) => (s, l),
|
Import::Plain(s, l) => (s, vec![], l),
|
||||||
Import::GlobalSymbol(s, _, l) => (s, l),
|
Import::GlobalSymbol(s, i, l) => (s, vec![(i, None)], l),
|
||||||
Import::Rename(s, _, l) => (s, l),
|
Import::Rename(s, i, l) => (s, i, l),
|
||||||
};
|
};
|
||||||
imports
|
let sol_import = SolImport::new(PathBuf::from(import.string))
|
||||||
.push(SolDataUnit::new(PathBuf::from(import.string), loc.into()));
|
.set_aliases(
|
||||||
|
ids.into_iter()
|
||||||
|
.map(|(id, alias)| match alias {
|
||||||
|
Some(al) => SolImportAlias::Contract(al.name, id.name),
|
||||||
|
None => SolImportAlias::File(id.name),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
imports.push(SolDataUnit::from_loc(sol_import, loc));
|
||||||
}
|
}
|
||||||
SourceUnitPart::ContractDefinition(def) => {
|
SourceUnitPart::ContractDefinition(def) => {
|
||||||
let functions = def
|
let functions = def
|
||||||
|
@ -100,16 +110,14 @@ impl SolData {
|
||||||
version =
|
version =
|
||||||
capture_outer_and_inner(content, &utils::RE_SOL_PRAGMA_VERSION, &["version"])
|
capture_outer_and_inner(content, &utils::RE_SOL_PRAGMA_VERSION, &["version"])
|
||||||
.first()
|
.first()
|
||||||
.map(|(cap, name)| {
|
.map(|(cap, name)| SolDataUnit::new(name.as_str().to_owned(), cap.range()));
|
||||||
SolDataUnit::new(name.as_str().to_owned(), cap.to_owned().into())
|
|
||||||
});
|
|
||||||
imports = capture_imports(content);
|
imports = capture_imports(content);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let license = content.lines().next().and_then(|line| {
|
let license = content.lines().next().and_then(|line| {
|
||||||
capture_outer_and_inner(line, &utils::RE_SOL_SDPX_LICENSE_IDENTIFIER, &["license"])
|
capture_outer_and_inner(line, &utils::RE_SOL_SDPX_LICENSE_IDENTIFIER, &["license"])
|
||||||
.first()
|
.first()
|
||||||
.map(|(cap, l)| SolDataUnit::new(l.as_str().to_owned(), cap.to_owned().into()))
|
.map(|(cap, l)| SolDataUnit::new(l.as_str().to_owned(), cap.range()))
|
||||||
});
|
});
|
||||||
let version_req = version.as_ref().and_then(|v| Solc::version_req(v.data()).ok());
|
let version_req = version.as_ref().and_then(|v| Solc::version_req(v.data()).ok());
|
||||||
|
|
||||||
|
@ -130,6 +138,37 @@ pub struct SolContract {
|
||||||
pub functions: Vec<FunctionDefinition>,
|
pub functions: Vec<FunctionDefinition>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SolImport {
|
||||||
|
path: PathBuf,
|
||||||
|
aliases: Vec<SolImportAlias>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum SolImportAlias {
|
||||||
|
File(String),
|
||||||
|
Contract(String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SolImport {
|
||||||
|
pub fn new(path: PathBuf) -> Self {
|
||||||
|
Self { path, aliases: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &PathBuf {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn aliases(&self) -> &Vec<SolImportAlias> {
|
||||||
|
&self.aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_aliases(mut self, aliases: Vec<SolImportAlias>) -> Self {
|
||||||
|
self.aliases = aliases;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Minimal representation of a contract inside a solidity file
|
/// Minimal representation of a contract inside a solidity file
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SolLibrary {
|
pub struct SolLibrary {
|
||||||
|
@ -164,57 +203,41 @@ impl SolLibrary {
|
||||||
/// Represents an item in a solidity file with its location in the file
|
/// Represents an item in a solidity file with its location in the file
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SolDataUnit<T> {
|
pub struct SolDataUnit<T> {
|
||||||
loc: Location,
|
loc: Range<usize>,
|
||||||
data: T,
|
data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Location in a text file buffer
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Location {
|
|
||||||
pub start: usize,
|
|
||||||
pub end: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Solidity Data Unit decorated with its location within the file
|
/// Solidity Data Unit decorated with its location within the file
|
||||||
impl<T> SolDataUnit<T> {
|
impl<T> SolDataUnit<T> {
|
||||||
pub fn new(data: T, loc: Location) -> Self {
|
pub fn new(data: T, loc: Range<usize>) -> Self {
|
||||||
Self { data, loc }
|
Self { data, loc }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_loc(data: T, loc: Loc) -> Self {
|
||||||
|
Self {
|
||||||
|
data,
|
||||||
|
loc: match loc {
|
||||||
|
Loc::File(_, start, end) => Range { start, end: end + 1 },
|
||||||
|
_ => Range { start: 0, end: 0 },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the underlying data for the unit
|
/// Returns the underlying data for the unit
|
||||||
pub fn data(&self) -> &T {
|
pub fn data(&self) -> &T {
|
||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the location of the given data unit
|
/// Returns the location of the given data unit
|
||||||
pub fn loc(&self) -> (usize, usize) {
|
pub fn loc(&self) -> Range<usize> {
|
||||||
(self.loc.start, self.loc.end)
|
self.loc.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the location of the given data unit adjusted by an offset.
|
/// Returns the location of the given data unit adjusted by an offset.
|
||||||
/// Used to determine new position of the unit within the file after
|
/// Used to determine new position of the unit within the file after
|
||||||
/// content manipulation.
|
/// content manipulation.
|
||||||
pub fn loc_by_offset(&self, offset: isize) -> (usize, usize) {
|
pub fn loc_by_offset(&self, offset: isize) -> Range<usize> {
|
||||||
(
|
utils::range_by_offset(&self.loc, offset)
|
||||||
offset.saturating_add(self.loc.start as isize) as usize,
|
|
||||||
// make the end location exclusive
|
|
||||||
offset.saturating_add(self.loc.end as isize + 1) as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Match<'_>> for Location {
|
|
||||||
fn from(src: Match) -> Self {
|
|
||||||
Location { start: src.start(), end: src.end() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Loc> for Location {
|
|
||||||
fn from(src: Loc) -> Self {
|
|
||||||
match src {
|
|
||||||
Loc::File(_, start, end) => Location { start, end },
|
|
||||||
_ => Location { start: 0, end: 0 },
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,12 +261,31 @@ fn capture_outer_and_inner<'a>(
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
/// Capture the import statement information together with aliases
|
||||||
pub fn capture_imports(content: &str) -> Vec<SolDataUnit<PathBuf>> {
|
pub fn capture_imports(content: &str) -> Vec<SolDataUnit<SolImport>> {
|
||||||
capture_outer_and_inner(content, &utils::RE_SOL_IMPORT, &["p1", "p2", "p3", "p4"])
|
let mut imports = vec![];
|
||||||
.iter()
|
for cap in utils::RE_SOL_IMPORT.captures_iter(content) {
|
||||||
.map(|(cap, m)| SolDataUnit::new(PathBuf::from(m.as_str()), cap.to_owned().into()))
|
if let Some(name_match) =
|
||||||
.collect()
|
vec!["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(SolDataUnit::new(sol_import, statement_match.range()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imports
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -259,7 +301,7 @@ import {DsTest} from "ds-test/test.sol";
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let captured_imports =
|
let captured_imports =
|
||||||
capture_imports(content).into_iter().map(|s| s.data).collect::<Vec<_>>();
|
capture_imports(content).into_iter().map(|s| s.data.path).collect::<Vec<_>>();
|
||||||
|
|
||||||
let expected =
|
let expected =
|
||||||
utils::find_import_paths(content).map(|m| m.as_str().into()).collect::<Vec<PathBuf>>();
|
utils::find_import_paths(content).map(|m| m.as_str().into()).collect::<Vec<PathBuf>>();
|
||||||
|
@ -275,4 +317,29 @@ import {DsTest} from "ds-test/test.sol";
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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::<Vec<_>>();
|
||||||
|
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()),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
|
ops::Range,
|
||||||
path::{Component, Path, PathBuf},
|
path::{Component, Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,9 +18,13 @@ use walkdir::WalkDir;
|
||||||
/// statement with the named groups "path", "id".
|
/// statement with the named groups "path", "id".
|
||||||
// Adapted from <https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L100>
|
// Adapted from <https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L100>
|
||||||
pub static RE_SOL_IMPORT: Lazy<Regex> = Lazy::new(|| {
|
pub static RE_SOL_IMPORT: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::new(r#"import\s+(?:(?:"(?P<p1>[^;]*)"|'(?P<p2>[^;]*)')(?:;|\s+as\s+(?P<id>[^;]*);)|.+from\s+(?:"(?P<p3>.*)"|'(?P<p4>.*)');)"#).unwrap()
|
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()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// A regex that matches an alias within an import statement
|
||||||
|
pub static RE_SOL_IMPORT_ALIAS: Lazy<Regex> =
|
||||||
|
Lazy::new(|| Regex::new(r#"(?:(?P<target>\w+)|\*|'|")\s+as\s+(?P<alias>\w+)"#).unwrap());
|
||||||
|
|
||||||
/// A regex that matches the version part of a solidity pragma
|
/// A regex that matches the version part of a solidity pragma
|
||||||
/// as follows: `pragma solidity ^0.5.2;` => `^0.5.2`
|
/// as follows: `pragma solidity ^0.5.2;` => `^0.5.2`
|
||||||
/// statement with the named group "version".
|
/// statement with the named group "version".
|
||||||
|
@ -35,6 +40,19 @@ pub static RE_SOL_SDPX_LICENSE_IDENTIFIER: Lazy<Regex> =
|
||||||
/// A regex used to remove extra lines in flatenned files
|
/// A regex used to remove extra lines in flatenned files
|
||||||
pub static RE_THREE_OR_MORE_NEWLINES: Lazy<Regex> = Lazy::new(|| Regex::new("\n{3,}").unwrap());
|
pub static RE_THREE_OR_MORE_NEWLINES: Lazy<Regex> = Lazy::new(|| Regex::new("\n{3,}").unwrap());
|
||||||
|
|
||||||
|
/// Create a regex that matches any library or contract name inside a file
|
||||||
|
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})(?:\.|\(| ))"#, name = name)).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move a range by a specified offset
|
||||||
|
pub fn range_by_offset(range: &Range<usize>, offset: isize) -> Range<usize> {
|
||||||
|
Range {
|
||||||
|
start: offset.saturating_add(range.start as isize) as usize,
|
||||||
|
end: offset.saturating_add(range.end as isize) as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns all path parts from any solidity import statement in a string,
|
/// Returns all path parts from any solidity import statement in a string,
|
||||||
/// `import "./contracts/Contract.sol";` -> `"./contracts/Contract.sol"`.
|
/// `import "./contracts/Contract.sol";` -> `"./contracts/Contract.sol"`.
|
||||||
///
|
///
|
||||||
|
|
|
@ -721,6 +721,154 @@ contract A { }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_flatten_with_alias() {
|
||||||
|
let project = TempProject::dapptools().unwrap();
|
||||||
|
|
||||||
|
let f = project
|
||||||
|
.add_source(
|
||||||
|
"Contract",
|
||||||
|
r#"pragma solidity ^0.8.10;
|
||||||
|
import { ParentContract as Parent } from "./Parent.sol";
|
||||||
|
import { AnotherParentContract as AnotherParent } from "./AnotherParent.sol";
|
||||||
|
import { PeerContract as Peer } from "./Peer.sol";
|
||||||
|
import { MathLibrary as Math } from "./Math.sol";
|
||||||
|
import * as Lib from "./SomeLib.sol";
|
||||||
|
|
||||||
|
contract Contract is Parent,
|
||||||
|
AnotherParent {
|
||||||
|
using Math for uint256;
|
||||||
|
|
||||||
|
string public usingString = "using Math for uint256;";
|
||||||
|
string public inheritanceString = "\"Contract is Parent {\"";
|
||||||
|
string public castString = 'Peer(smth) ';
|
||||||
|
string public methodString = '\' Math.max()';
|
||||||
|
|
||||||
|
Peer public peer;
|
||||||
|
|
||||||
|
error Peer();
|
||||||
|
|
||||||
|
constructor(address _peer) {
|
||||||
|
peer = Peer(_peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Math(uint256 value) external pure returns (uint256) {
|
||||||
|
return Math.minusOne(Math.max() - value.diffMax());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
project
|
||||||
|
.add_source(
|
||||||
|
"Parent",
|
||||||
|
r#"pragma solidity ^0.8.10;
|
||||||
|
contract ParentContract { }
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
project
|
||||||
|
.add_source(
|
||||||
|
"AnotherParent",
|
||||||
|
r#"pragma solidity ^0.8.10;
|
||||||
|
contract AnotherParentContract { }
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
project
|
||||||
|
.add_source(
|
||||||
|
"Peer",
|
||||||
|
r#"pragma solidity ^0.8.10;
|
||||||
|
contract PeerContract { }
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
project
|
||||||
|
.add_source(
|
||||||
|
"Math",
|
||||||
|
r#"pragma solidity ^0.8.10;
|
||||||
|
library MathLibrary {
|
||||||
|
function minusOne(uint256 val) internal returns (uint256) {
|
||||||
|
return val - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function max() internal returns (uint256) {
|
||||||
|
return type(uint256).max;
|
||||||
|
}
|
||||||
|
|
||||||
|
function diffMax(uint256 value) internal returns (uint256) {
|
||||||
|
return type(uint256).max - value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
project
|
||||||
|
.add_source(
|
||||||
|
"SomeLib",
|
||||||
|
r#"pragma solidity ^0.8.10;
|
||||||
|
library SomeLib { }
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let result = project.flatten(&f).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
r#"pragma solidity ^0.8.10;
|
||||||
|
|
||||||
|
contract ParentContract { }
|
||||||
|
|
||||||
|
contract AnotherParentContract { }
|
||||||
|
|
||||||
|
contract PeerContract { }
|
||||||
|
|
||||||
|
library MathLibrary {
|
||||||
|
function minusOne(uint256 val) internal returns (uint256) {
|
||||||
|
return val - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function max() internal returns (uint256) {
|
||||||
|
return type(uint256).max;
|
||||||
|
}
|
||||||
|
|
||||||
|
function diffMax(uint256 value) internal returns (uint256) {
|
||||||
|
return type(uint256).max - value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
library SomeLib { }
|
||||||
|
|
||||||
|
contract Contract is ParentContract,
|
||||||
|
AnotherParentContract {
|
||||||
|
using MathLibrary for uint256;
|
||||||
|
|
||||||
|
string public usingString = "using Math for uint256;";
|
||||||
|
string public inheritanceString = "\"Contract is Parent {\"";
|
||||||
|
string public castString = 'Peer(smth) ';
|
||||||
|
string public methodString = '\' Math.max()';
|
||||||
|
|
||||||
|
PeerContract public peer;
|
||||||
|
|
||||||
|
error Peer();
|
||||||
|
|
||||||
|
constructor(address _peer) {
|
||||||
|
peer = PeerContract(_peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Math(uint256 value) external pure returns (uint256) {
|
||||||
|
return MathLibrary.minusOne(MathLibrary.max() - value.diffMax());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_detect_type_error() {
|
fn can_detect_type_error() {
|
||||||
let project = TempProject::<ConfigurableArtifacts>::dapptools().unwrap();
|
let project = TempProject::<ConfigurableArtifacts>::dapptools().unwrap();
|
||||||
|
|
Loading…
Reference in New Issue