1use crate::solang_ext::{Visitable, Visitor};
4use itertools::Itertools;
5use solang_parser::{
6 doccomment::{DocComment, parse_doccomments},
7 pt::{
8 Comment as SolangComment, EnumDefinition, ErrorDefinition, EventDefinition,
9 FunctionDefinition, Identifier, Loc, SourceUnit, SourceUnitPart, StructDefinition,
10 TypeDefinition, VariableDefinition,
11 },
12};
13
14pub mod error;
16use error::{ParserError, ParserResult};
17
18mod item;
20pub use item::{ParseItem, ParseSource};
21
22mod comment;
24pub use comment::{Comment, CommentTag, Comments, CommentsRef};
25
26#[derive(Debug, Default)]
31pub struct Parser {
32 comments: Vec<SolangComment>,
34 context: ParserContext,
36 items: Vec<ParseItem>,
38 source: String,
40}
41
42#[derive(Debug, Default)]
44struct ParserContext {
45 parent: Option<ParseItem>,
47 doc_start_loc: usize,
49}
50
51impl Parser {
52 pub fn new(comments: Vec<SolangComment>, source: String) -> Self {
54 Self { comments, source, ..Default::default() }
55 }
56
57 pub fn items(self) -> Vec<ParseItem> {
59 self.items
60 }
61
62 fn with_parent(
67 &mut self,
68 mut parent: ParseItem,
69 mut visit: impl FnMut(&mut Self) -> ParserResult<()>,
70 ) -> ParserResult<ParseItem> {
71 let curr = self.context.parent.take();
72 self.context.parent = Some(parent);
73 visit(self)?;
74 parent = self.context.parent.take().unwrap();
75 self.context.parent = curr;
76 Ok(parent)
77 }
78
79 fn add_element_to_parent(&mut self, source: ParseSource, loc: Loc) -> ParserResult<()> {
83 let child = self.new_item(source, loc.start())?;
84 if let Some(parent) = self.context.parent.as_mut() {
85 parent.children.push(child);
86 } else {
87 self.items.push(child);
88 }
89 self.context.doc_start_loc = loc.end();
90 Ok(())
91 }
92
93 fn new_item(&mut self, source: ParseSource, loc_start: usize) -> ParserResult<ParseItem> {
95 let docs = self.parse_docs(loc_start)?;
96 Ok(ParseItem::new(source).with_comments(docs).with_code(&self.source))
97 }
98
99 fn parse_docs(&mut self, end: usize) -> ParserResult<Comments> {
101 self.parse_docs_range(self.context.doc_start_loc, end)
102 }
103
104 fn parse_docs_range(&mut self, start: usize, end: usize) -> ParserResult<Comments> {
106 let mut res = vec![];
107 for comment in parse_doccomments(&self.comments, start, end) {
108 match comment {
109 DocComment::Line { comment } => res.push(comment),
110 DocComment::Block { comments } => res.extend(comments),
111 }
112 }
113
114 let res = res
117 .into_iter()
118 .filter(|c| c.tag.trim() != "solidity" && !c.tag.trim().is_empty())
119 .collect_vec();
120 Ok(res.into())
121 }
122}
123
124impl Visitor for Parser {
125 type Error = ParserError;
126
127 fn visit_source_unit(&mut self, source_unit: &mut SourceUnit) -> ParserResult<()> {
128 for source in &mut source_unit.0 {
129 match source {
130 SourceUnitPart::ContractDefinition(def) => {
131 let contract =
133 self.new_item(ParseSource::Contract(def.clone()), def.loc.start())?;
134
135 self.context.doc_start_loc = def.loc.start();
137
138 let contract = self.with_parent(contract, |doc| {
140 def.parts
141 .iter_mut()
142 .map(|d| d.visit(doc))
143 .collect::<ParserResult<Vec<_>>>()?;
144 Ok(())
145 })?;
146
147 self.context.doc_start_loc = def.loc.end();
149
150 self.items.push(contract);
152 }
153 SourceUnitPart::FunctionDefinition(func) => self.visit_function(func)?,
154 SourceUnitPart::EventDefinition(event) => self.visit_event(event)?,
155 SourceUnitPart::ErrorDefinition(error) => self.visit_error(error)?,
156 SourceUnitPart::StructDefinition(structure) => self.visit_struct(structure)?,
157 SourceUnitPart::EnumDefinition(enumerable) => self.visit_enum(enumerable)?,
158 SourceUnitPart::VariableDefinition(var) => self.visit_var_definition(var)?,
159 SourceUnitPart::TypeDefinition(ty) => self.visit_type_definition(ty)?,
160 _ => {}
161 };
162 }
163
164 Ok(())
165 }
166
167 fn visit_enum(&mut self, enumerable: &mut EnumDefinition) -> ParserResult<()> {
168 self.add_element_to_parent(ParseSource::Enum(enumerable.clone()), enumerable.loc)
169 }
170
171 fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> ParserResult<()> {
172 self.add_element_to_parent(ParseSource::Variable(var.clone()), var.loc)
173 }
174
175 fn visit_function(&mut self, func: &mut FunctionDefinition) -> ParserResult<()> {
176 let mut start_loc = func.loc.start();
179 for (loc, param) in &mut func.params {
180 if let Some(param) = param
181 && param.name.is_none()
182 {
183 let docs = self.parse_docs_range(start_loc, loc.end())?;
184 let name_tag = docs.iter().find(|c| c.tag == CommentTag::Custom("name".to_owned()));
185 if let Some(name_tag) = name_tag
186 && let Some(name) = name_tag.value.trim().split(' ').next()
187 {
188 param.name = Some(Identifier { loc: Loc::Implicit, name: name.to_owned() })
189 }
190 }
191 start_loc = loc.end();
192 }
193
194 self.add_element_to_parent(ParseSource::Function(func.clone()), func.loc)
195 }
196
197 fn visit_struct(&mut self, structure: &mut StructDefinition) -> ParserResult<()> {
198 self.add_element_to_parent(ParseSource::Struct(structure.clone()), structure.loc)
199 }
200
201 fn visit_event(&mut self, event: &mut EventDefinition) -> ParserResult<()> {
202 self.add_element_to_parent(ParseSource::Event(event.clone()), event.loc)
203 }
204
205 fn visit_error(&mut self, error: &mut ErrorDefinition) -> ParserResult<()> {
206 self.add_element_to_parent(ParseSource::Error(error.clone()), error.loc)
207 }
208
209 fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> ParserResult<()> {
210 self.add_element_to_parent(ParseSource::Type(def.clone()), def.loc)
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use solang_parser::parse;
218
219 fn parse_source(src: &str) -> Vec<ParseItem> {
220 let (mut source, comments) = parse(src, 0).expect("failed to parse source");
221 let mut doc = Parser::new(comments, src.to_owned());
222 source.visit(&mut doc).expect("failed to visit source");
223 doc.items()
224 }
225
226 macro_rules! test_single_unit {
227 ($test:ident, $src:expr, $variant:ident $identity:expr) => {
228 #[test]
229 fn $test() {
230 let items = parse_source($src);
231 assert_eq!(items.len(), 1);
232 let item = items.first().unwrap();
233 assert!(item.comments.is_empty());
234 assert!(item.children.is_empty());
235 assert_eq!(item.source.ident(), $identity);
236 assert!(matches!(item.source, ParseSource::$variant(_)));
237 }
238 };
239 }
240
241 #[test]
242 fn empty_source() {
243 assert_eq!(parse_source(""), vec![]);
244 }
245
246 test_single_unit!(single_function, "function someFn() { }", Function "someFn");
247 test_single_unit!(single_variable, "uint256 constant VALUE = 0;", Variable "VALUE");
248 test_single_unit!(single_event, "event SomeEvent();", Event "SomeEvent");
249 test_single_unit!(single_error, "error SomeError();", Error "SomeError");
250 test_single_unit!(single_struct, "struct SomeStruct { }", Struct "SomeStruct");
251 test_single_unit!(single_enum, "enum SomeEnum { SOME, OTHER }", Enum "SomeEnum");
252 test_single_unit!(single_contract, "contract Contract { }", Contract "Contract");
253
254 #[test]
255 fn multiple_shallow_contracts() {
256 let items = parse_source(
257 r"
258 contract A { }
259 contract B { }
260 contract C { }
261 ",
262 );
263 assert_eq!(items.len(), 3);
264
265 let first_item = items.first().unwrap();
266 assert!(matches!(first_item.source, ParseSource::Contract(_)));
267 assert_eq!(first_item.source.ident(), "A");
268
269 let first_item = items.get(1).unwrap();
270 assert!(matches!(first_item.source, ParseSource::Contract(_)));
271 assert_eq!(first_item.source.ident(), "B");
272
273 let first_item = items.get(2).unwrap();
274 assert!(matches!(first_item.source, ParseSource::Contract(_)));
275 assert_eq!(first_item.source.ident(), "C");
276 }
277
278 #[test]
279 fn contract_with_children_items() {
280 let items = parse_source(
281 r"
282 event TopLevelEvent();
283
284 contract Contract {
285 event ContractEvent();
286 error ContractError();
287 struct ContractStruct { }
288 enum ContractEnum { }
289
290 uint256 constant CONTRACT_CONSTANT = 0;
291 bool contractVar;
292
293 function contractFunction(uint256) external returns (uint256) {
294 bool localVar; // must be ignored
295 }
296 }
297 ",
298 );
299
300 assert_eq!(items.len(), 2);
301
302 let event = items.first().unwrap();
303 assert!(event.comments.is_empty());
304 assert!(event.children.is_empty());
305 assert_eq!(event.source.ident(), "TopLevelEvent");
306 assert!(matches!(event.source, ParseSource::Event(_)));
307
308 let contract = items.get(1).unwrap();
309 assert!(contract.comments.is_empty());
310 assert_eq!(contract.children.len(), 7);
311 assert_eq!(contract.source.ident(), "Contract");
312 assert!(matches!(contract.source, ParseSource::Contract(_)));
313 assert!(contract.children.iter().all(|ch| ch.children.is_empty()));
314 assert!(contract.children.iter().all(|ch| ch.comments.is_empty()));
315 }
316
317 #[test]
318 fn contract_with_fallback() {
319 let items = parse_source(
320 r"
321 contract Contract {
322 fallback() external payable {}
323 }
324 ",
325 );
326
327 assert_eq!(items.len(), 1);
328
329 let contract = items.first().unwrap();
330 assert!(contract.comments.is_empty());
331 assert_eq!(contract.children.len(), 1);
332 assert_eq!(contract.source.ident(), "Contract");
333 assert!(matches!(contract.source, ParseSource::Contract(_)));
334
335 let fallback = contract.children.first().unwrap();
336 assert_eq!(fallback.source.ident(), "fallback");
337 assert!(matches!(fallback.source, ParseSource::Function(_)));
338 }
339
340 #[test]
341 fn contract_with_doc_comments() {
342 let items = parse_source(
343 r"
344 pragma solidity ^0.8.19;
345 /// @name Test
346 /// no tag
347 ///@notice Cool contract
348 /// @ dev This is not a dev tag
349 /**
350 * @dev line one
351 * line 2
352 */
353 contract Test {
354 /** my function
355 i like whitespace
356 */
357 function test() {}
358 }
359 ",
360 );
361
362 assert_eq!(items.len(), 1);
363
364 let contract = items.first().unwrap();
365 assert_eq!(contract.comments.len(), 2);
366 assert_eq!(
367 *contract.comments.first().unwrap(),
368 Comment::new(CommentTag::Notice, "Cool contract".to_owned())
369 );
370 assert_eq!(
371 *contract.comments.get(1).unwrap(),
372 Comment::new(CommentTag::Dev, "line one\nline 2".to_owned())
373 );
374
375 let function = contract.children.first().unwrap();
376 assert_eq!(
377 *function.comments.first().unwrap(),
378 Comment::new(CommentTag::Notice, "my function\ni like whitespace".to_owned())
379 );
380 }
381}