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