1use crate::{utils, Solc};
2use semver::VersionReq;
3use solang_parser::pt::{
4 ContractPart, ContractTy, FunctionAttribute, FunctionDefinition, Import, ImportPath, Loc,
5 SourceUnitPart, Visibility,
6};
7use std::{
8 ops::Range,
9 path::{Path, PathBuf},
10};
11
12#[derive(Debug)]
14#[allow(unused)]
15pub struct SolData {
16 pub license: Option<SolDataUnit<String>>,
17 pub version: Option<SolDataUnit<String>>,
18 pub experimental: Option<SolDataUnit<String>>,
19 pub imports: Vec<SolDataUnit<SolImport>>,
20 pub version_req: Option<VersionReq>,
21 pub libraries: Vec<SolLibrary>,
22 pub contracts: Vec<SolContract>,
23}
24
25impl SolData {
26 #[allow(unused)]
27 pub fn fmt_version<W: std::fmt::Write>(
28 &self,
29 f: &mut W,
30 ) -> std::result::Result<(), std::fmt::Error> {
31 if let Some(ref version) = self.version {
32 write!(f, "({})", version.data)?;
33 }
34 Ok(())
35 }
36
37 pub fn parse(content: &str, file: &Path) -> Self {
42 let mut version = None;
43 let mut experimental = None;
44 let mut imports = Vec::<SolDataUnit<SolImport>>::new();
45 let mut libraries = Vec::new();
46 let mut contracts = Vec::new();
47
48 match solang_parser::parse(content, 0) {
49 Ok((units, _)) => {
50 for unit in units.0 {
51 match unit {
52 SourceUnitPart::PragmaDirective(loc, Some(pragma), Some(value)) => {
53 if pragma.name == "solidity" {
54 version = Some(SolDataUnit::from_loc(value.string.clone(), loc));
56 }
57
58 if pragma.name == "experimental" {
59 experimental = Some(SolDataUnit::from_loc(value.string, loc));
60 }
61 }
62 SourceUnitPart::ImportDirective(import) => {
63 let (import, ids, loc) = match import {
64 Import::Plain(s, l) => (s, vec![], l),
65 Import::GlobalSymbol(s, i, l) => (s, vec![(i, None)], l),
66 Import::Rename(s, i, l) => (s, i, l),
67 };
68 let import = match import {
69 ImportPath::Filename(s) => s.string.clone(),
70 ImportPath::Path(p) => p.to_string(),
71 };
72 let sol_import = SolImport::new(PathBuf::from(import)).set_aliases(
73 ids.into_iter()
74 .map(|(id, alias)| match alias {
75 Some(al) => SolImportAlias::Contract(al.name, id.name),
76 None => SolImportAlias::File(id.name),
77 })
78 .collect(),
79 );
80 imports.push(SolDataUnit::from_loc(sol_import, loc));
81 }
82 SourceUnitPart::ContractDefinition(def) => {
83 let functions = def
84 .parts
85 .into_iter()
86 .filter_map(|part| match part {
87 ContractPart::FunctionDefinition(f) => Some(*f),
88 _ => None,
89 })
90 .collect();
91 if let Some(name) = def.name {
92 match def.ty {
93 ContractTy::Contract(_) => {
94 contracts.push(SolContract { name: name.name, functions });
95 }
96 ContractTy::Library(_) => {
97 libraries.push(SolLibrary { name: name.name, functions });
98 }
99 _ => {}
100 }
101 }
102 }
103 _ => {}
104 }
105 }
106 }
107 Err(err) => {
108 tracing::trace!(
109 "failed to parse \"{}\" ast: \"{:?}\". Falling back to regex to extract data",
110 file.display(),
111 err
112 );
113 version =
114 capture_outer_and_inner(content, &utils::RE_SOL_PRAGMA_VERSION, &["version"])
115 .first()
116 .map(|(cap, name)| SolDataUnit::new(name.as_str().to_owned(), cap.range()));
117 imports = capture_imports(content);
118 }
119 };
120 let license = content.lines().next().and_then(|line| {
121 capture_outer_and_inner(line, &utils::RE_SOL_SDPX_LICENSE_IDENTIFIER, &["license"])
122 .first()
123 .map(|(cap, l)| SolDataUnit::new(l.as_str().to_owned(), cap.range()))
124 });
125 let version_req = version.as_ref().and_then(|v| Solc::version_req(v.data()).ok());
126
127 Self { version_req, version, experimental, imports, license, libraries, contracts }
128 }
129
130 pub fn has_link_references(&self) -> bool {
133 self.libraries.iter().any(|lib| !lib.is_inlined())
134 }
135}
136
137#[derive(Debug)]
139pub struct SolContract {
140 pub name: String,
141 pub functions: Vec<FunctionDefinition>,
142}
143
144#[derive(Debug, Clone)]
145pub struct SolImport {
146 path: PathBuf,
147 aliases: Vec<SolImportAlias>,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub enum SolImportAlias {
152 File(String),
153 Contract(String, String),
154}
155
156impl SolImport {
157 pub fn new(path: PathBuf) -> Self {
158 Self { path, aliases: vec![] }
159 }
160
161 pub fn path(&self) -> &PathBuf {
162 &self.path
163 }
164
165 pub fn aliases(&self) -> &Vec<SolImportAlias> {
166 &self.aliases
167 }
168
169 fn set_aliases(mut self, aliases: Vec<SolImportAlias>) -> Self {
170 self.aliases = aliases;
171 self
172 }
173}
174
175#[derive(Debug)]
177pub struct SolLibrary {
178 pub name: String,
179 pub functions: Vec<FunctionDefinition>,
180}
181
182impl SolLibrary {
183 pub fn is_inlined(&self) -> bool {
192 for f in self.functions.iter() {
193 for attr in f.attributes.iter() {
194 if let FunctionAttribute::Visibility(vis) = attr {
195 match vis {
196 Visibility::External(_) | Visibility::Public(_) => return false,
197 _ => {}
198 }
199 }
200 }
201 }
202 true
203 }
204}
205
206#[derive(Debug, Clone)]
208pub struct SolDataUnit<T> {
209 loc: Range<usize>,
210 data: T,
211}
212
213impl<T> SolDataUnit<T> {
215 pub fn new(data: T, loc: Range<usize>) -> Self {
216 Self { data, loc }
217 }
218
219 pub fn from_loc(data: T, loc: Loc) -> Self {
220 Self {
221 data,
222 loc: match loc {
223 Loc::File(_, start, end) => Range { start, end: end + 1 },
224 _ => Range { start: 0, end: 0 },
225 },
226 }
227 }
228
229 pub fn data(&self) -> &T {
231 &self.data
232 }
233
234 pub fn loc(&self) -> Range<usize> {
236 self.loc.clone()
237 }
238
239 pub fn loc_by_offset(&self, offset: isize) -> Range<usize> {
243 utils::range_by_offset(&self.loc, offset)
244 }
245}
246
247fn capture_outer_and_inner<'a>(
255 content: &'a str,
256 regex: ®ex::Regex,
257 names: &[&str],
258) -> Vec<(regex::Match<'a>, regex::Match<'a>)> {
259 regex
260 .captures_iter(content)
261 .filter_map(|cap| {
262 let cap_match = names.iter().find_map(|name| cap.name(name));
263 cap_match.and_then(|m| cap.get(0).map(|outer| (outer.to_owned(), m)))
264 })
265 .collect()
266}
267pub fn capture_imports(content: &str) -> Vec<SolDataUnit<SolImport>> {
269 let mut imports = vec![];
270 for cap in utils::RE_SOL_IMPORT.captures_iter(content) {
271 if let Some(name_match) = ["p1", "p2", "p3", "p4"].iter().find_map(|name| cap.name(name)) {
272 let statement_match = cap.get(0).unwrap();
273 let mut aliases = vec![];
274 for alias_cap in utils::RE_SOL_IMPORT_ALIAS.captures_iter(statement_match.as_str()) {
275 if let Some(alias) = alias_cap.name("alias") {
276 let alias = alias.as_str().to_owned();
277 let import_alias = match alias_cap.name("target") {
278 Some(target) => SolImportAlias::Contract(alias, target.as_str().to_owned()),
279 None => SolImportAlias::File(alias),
280 };
281 aliases.push(import_alias);
282 }
283 }
284 let sol_import =
285 SolImport::new(PathBuf::from(name_match.as_str())).set_aliases(aliases);
286 imports.push(SolDataUnit::new(sol_import, statement_match.range()));
287 }
288 }
289 imports
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 #[test]
297 fn can_capture_curly_imports() {
298 let content = r#"
299import { T } from "../Test.sol";
300import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
301import {DsTest} from "ds-test/test.sol";
302"#;
303
304 let captured_imports =
305 capture_imports(content).into_iter().map(|s| s.data.path).collect::<Vec<_>>();
306
307 let expected =
308 utils::find_import_paths(content).map(|m| m.as_str().into()).collect::<Vec<PathBuf>>();
309
310 assert_eq!(captured_imports, expected);
311
312 assert_eq!(
313 captured_imports,
314 vec![
315 PathBuf::from("../Test.sol"),
316 "@openzeppelin/contracts/utils/ReentrancyGuard.sol".into(),
317 "ds-test/test.sol".into(),
318 ]
319 );
320 }
321
322 #[test]
323 fn cap_capture_aliases() {
324 let content = r#"
325import * as T from "./Test.sol";
326import { DsTest as Test } from "ds-test/test.sol";
327import "ds-test/test.sol" as Test;
328import { FloatMath as Math, Math as FloatMath } from "./Math.sol";
329"#;
330
331 let caputred_imports =
332 capture_imports(content).into_iter().map(|s| s.data.aliases).collect::<Vec<_>>();
333 assert_eq!(
334 caputred_imports,
335 vec![
336 vec![SolImportAlias::File("T".into())],
337 vec![SolImportAlias::Contract("Test".into(), "DsTest".into())],
338 vec![SolImportAlias::File("Test".into())],
339 vec![
340 SolImportAlias::Contract("Math".into(), "FloatMath".into()),
341 SolImportAlias::Contract("FloatMath".into(), "Math".into()),
342 ],
343 ]
344 );
345 }
346}