1use std::{
5 collections::HashMap,
6 io,
7 path::{Path, PathBuf},
8 sync::{Arc, Mutex},
9};
10
11use slang_solidity::{
12 cst::{
13 Cursor, NonterminalKind, Query, QueryMatch, TerminalKind, TextIndex as SlangTextIndex,
14 TextRange as SlangTextRange,
15 },
16 parser::Parser,
17};
18
19use crate::{
20 definitions::{
21 Attributes, Definition, Identifier, Parent, Visibility, constructor::ConstructorDefinition,
22 contract::ContractDefinition, enumeration::EnumDefinition, error::ErrorDefinition,
23 event::EventDefinition, function::FunctionDefinition, interface::InterfaceDefinition,
24 library::LibraryDefinition, modifier::ModifierDefinition, structure::StructDefinition,
25 variable::VariableDeclaration,
26 },
27 error::{ErrorKind, Result},
28 interner::INTERNER,
29 natspec::{NatSpec, parse_comment},
30 parser::DocumentId,
31 prelude::OrPanic as _,
32 textindex::{TextIndex, TextRange},
33 utils::{detect_solidity_version, get_latest_supported_version},
34};
35
36use super::{Parse, ParsedDocument, complete_text_ranges};
37
38#[derive(Debug, Clone, Default, bon::Builder)]
40#[non_exhaustive]
41pub struct SlangParser {
42 #[builder(default)]
43 pub skip_version_detection: bool,
44
45 #[builder(skip)]
46 documents: Arc<Mutex<HashMap<DocumentId, String>>>,
47}
48
49impl SlangParser {
50 #[must_use]
52 pub fn queries() -> Vec<Query> {
53 vec![
54 ConstructorDefinition::query(),
55 EnumDefinition::query(),
56 ErrorDefinition::query(),
57 EventDefinition::query(),
58 FunctionDefinition::query(),
59 ModifierDefinition::query(),
60 StructDefinition::query(),
61 VariableDeclaration::query(),
62 ContractDefinition::query(),
63 InterfaceDefinition::query(),
64 LibraryDefinition::query(),
65 ]
66 }
67
68 #[must_use]
70 pub fn find_items(cursor: Cursor) -> Vec<Definition> {
71 let mut out = Vec::new();
72 for m in cursor.query(Self::queries()) {
73 let def = match m.query_index() {
74 0 => Some(
75 ConstructorDefinition::extract(m)
76 .unwrap_or_else(|e| Definition::NatspecParsingError(e.into_inner())),
77 ),
78 1 => Some(
79 EnumDefinition::extract(m)
80 .unwrap_or_else(|e| Definition::NatspecParsingError(e.into_inner())),
81 ),
82 2 => Some(
83 ErrorDefinition::extract(m)
84 .unwrap_or_else(|e| Definition::NatspecParsingError(e.into_inner())),
85 ),
86 3 => Some(
87 EventDefinition::extract(m)
88 .unwrap_or_else(|e| Definition::NatspecParsingError(e.into_inner())),
89 ),
90 4 => {
91 let def = FunctionDefinition::extract(m)
92 .unwrap_or_else(|e| Definition::NatspecParsingError(e.into_inner()));
93 if out.contains(&def) { None } else { Some(def) }
94 }
95 5 => {
96 let def = ModifierDefinition::extract(m)
97 .unwrap_or_else(|e| Definition::NatspecParsingError(e.into_inner()));
98 if out.contains(&def) { None } else { Some(def) }
99 }
100 6 => Some(
101 StructDefinition::extract(m)
102 .unwrap_or_else(|e| Definition::NatspecParsingError(e.into_inner())),
103 ),
104 7 => Some(
105 VariableDeclaration::extract(m)
106 .unwrap_or_else(|e| Definition::NatspecParsingError(e.into_inner())),
107 ),
108 8 => {
109 let def = ContractDefinition::extract(m)
110 .unwrap_or_else(|e| Definition::NatspecParsingError(e.into_inner()));
111 if out.contains(&def) { None } else { Some(def) }
112 }
113 9 => {
114 let def = InterfaceDefinition::extract(m)
115 .unwrap_or_else(|e| Definition::NatspecParsingError(e.into_inner()));
116 if out.contains(&def) { None } else { Some(def) }
117 }
118 10 => Some(
119 LibraryDefinition::extract(m)
120 .unwrap_or_else(|e| Definition::NatspecParsingError(e.into_inner())),
121 ),
122 _ => unreachable!(),
123 };
124 if let Some(def) = def {
125 out.push(def);
126 }
127 }
128 out
129 }
130}
131
132impl Parse for SlangParser {
133 fn parse_document(
134 &mut self,
135 input: impl io::Read,
136 path: Option<impl AsRef<Path>>,
137 keep_contents: bool,
138 ) -> Result<ParsedDocument> {
139 fn inner(
140 this: &mut SlangParser,
141 mut input: impl io::Read,
142 path: Option<PathBuf>,
143 keep_contents: bool,
144 ) -> Result<ParsedDocument> {
145 let path = path.unwrap_or(PathBuf::from("<stdin>"));
146 let mut source = String::new();
147 input
148 .read_to_string(&mut source)
149 .map_err(|err| ErrorKind::IOError {
150 path: PathBuf::new(),
151 err,
152 })?;
153 let solidity_version = if this.skip_version_detection {
154 get_latest_supported_version()
155 } else {
156 detect_solidity_version(&source, &path)?
157 };
158 let parser = Parser::create(solidity_version).or_panic("parser should initialize");
159 let output = parser.parse_file_contents(&source);
160 if !output.is_valid() {
161 let Some(error) = output.errors().first() else {
162 return Err(ErrorKind::UnknownError.into());
163 };
164 return Err(ErrorKind::ParsingError {
165 path,
166 loc: error.text_range().start.into(),
167 message: error.message(),
168 }
169 .into());
170 }
171 let document_id = DocumentId::new();
172 let cursor = output.create_tree_cursor();
173 let mut definitions = SlangParser::find_items(cursor);
174 complete_text_ranges(&source, &mut definitions);
175 if keep_contents {
176 let mut documents = this
177 .documents
178 .lock()
179 .or_panic("mutex should not be poisoned");
180 documents.insert(document_id, source);
181 }
182 Ok(ParsedDocument {
183 definitions,
184 id: document_id,
185 })
186 }
187 inner(
188 self,
189 input,
190 path.map(|p| p.as_ref().to_path_buf()),
191 keep_contents,
192 )
193 }
194
195 fn get_sources(self) -> Result<HashMap<DocumentId, String>> {
196 Ok(Arc::try_unwrap(self.documents)
197 .map_err(|_| ErrorKind::DanglingParserReferences)?
198 .into_inner()
199 .or_panic("mutex should not be poisoned"))
200 }
201}
202
203pub trait Extract {
205 fn query() -> Query;
207
208 fn extract(m: QueryMatch) -> Result<Definition>;
210}
211
212impl Extract for ConstructorDefinition {
213 fn query() -> Query {
214 Query::create(
215 "@constructor [ConstructorDefinition
216 parameters:[ParametersDeclaration
217 @constructor_params parameters:[Parameters]
218 ]
219 @constructor_attr attributes:[ConstructorAttributes]
220 ]",
221 )
222 .or_panic("query should compile")
223 }
224
225 fn extract(m: QueryMatch) -> Result<Definition> {
226 let constructor = capture(&m, "constructor")?;
227 let params = capture(&m, "constructor_params")?;
228 let attr = capture(&m, "constructor_attr")?;
229
230 let span_start = find_definition_start(&constructor);
231 let span_end = attr.text_range().end.into();
232 let span = span_start..span_end;
233
234 let params = extract_params(¶ms, NonterminalKind::Parameter);
235 let natspec = extract_comment(&constructor.clone(), &[])?;
236 let parent = extract_parent_name(constructor);
237
238 Ok(ConstructorDefinition {
239 parent,
240 span,
241 params,
242 natspec,
243 }
244 .into())
245 }
246}
247
248impl Extract for EnumDefinition {
249 fn query() -> Query {
250 Query::create(
251 "@enum [EnumDefinition
252 @enum_name name:[Identifier]
253 @enum_members members:[EnumMembers]
254 ]",
255 )
256 .or_panic("query should compile")
257 }
258
259 fn extract(m: QueryMatch) -> Result<Definition> {
260 let enumeration = capture(&m, "enum")?;
261 let name = capture(&m, "enum_name")?;
262 let members = capture(&m, "enum_members")?;
263
264 let span = find_definition_start(&enumeration)..find_definition_end(&enumeration);
265 let name = INTERNER.get_or_intern(name.node().unparse().trim());
266 let members = extract_enum_members(&members);
267 let natspec = extract_comment(&enumeration.clone(), &[])?;
268 let parent = extract_parent_name(enumeration);
269
270 Ok(EnumDefinition {
271 parent,
272 name,
273 span,
274 members,
275 natspec,
276 }
277 .into())
278 }
279}
280
281impl Extract for ErrorDefinition {
282 fn query() -> Query {
283 Query::create(
284 "@err [ErrorDefinition
285 @err_name name:[Identifier]
286 @err_params members:[ErrorParametersDeclaration]
287 ]",
288 )
289 .or_panic("query should compile")
290 }
291
292 fn extract(m: QueryMatch) -> Result<Definition> {
293 let err = capture(&m, "err")?;
294 let name = capture(&m, "err_name")?;
295 let params = capture(&m, "err_params")?;
296
297 let span = find_definition_start(&err)..find_definition_end(&err);
298 let name = INTERNER.get_or_intern(name.node().unparse().trim());
299 let params = extract_identifiers(¶ms);
300 let natspec = extract_comment(&err.clone(), &[])?;
301 let parent = extract_parent_name(err);
302
303 Ok(ErrorDefinition {
304 parent,
305 name,
306 span,
307 params,
308 natspec,
309 }
310 .into())
311 }
312}
313
314impl Extract for EventDefinition {
315 fn query() -> Query {
316 Query::create(
317 "@event [EventDefinition
318 @event_name name:[Identifier]
319 @event_params parameters:[EventParametersDeclaration]
320 ]",
321 )
322 .or_panic("query should compile")
323 }
324
325 fn extract(m: QueryMatch) -> Result<Definition> {
326 let event = capture(&m, "event")?;
327 let name = capture(&m, "event_name")?;
328 let params = capture(&m, "event_params")?;
329
330 let span = find_definition_start(&event)..find_definition_end(&event);
331 let name = INTERNER.get_or_intern(name.node().unparse().trim());
332 let params = extract_params(¶ms, NonterminalKind::EventParameter);
333 let natspec = extract_comment(&event.clone(), &[])?;
334 let parent = extract_parent_name(event);
335
336 Ok(EventDefinition {
337 parent,
338 name,
339 span,
340 params,
341 natspec,
342 }
343 .into())
344 }
345}
346
347impl Extract for FunctionDefinition {
348 fn query() -> Query {
349 Query::create(
350 "@function [FunctionDefinition
351 @keyword function_keyword:[FunctionKeyword]
352 @function_name name:[FunctionName]
353 parameters:[ParametersDeclaration
354 @function_params parameters:[Parameters]
355 ]
356 @function_attr attributes:[FunctionAttributes]
357 returns:[ReturnsDeclaration
358 @function_returns_declaration variables:[ParametersDeclaration
359 @function_returns parameters:[Parameters]
360 ]
361 ]?
362 ]",
363 )
364 .or_panic("query should compile")
365 }
366
367 fn extract(m: QueryMatch) -> Result<Definition> {
368 let func = capture(&m, "function")?;
369 let name = capture(&m, "function_name")?;
370 let params = capture(&m, "function_params")?;
371 let attributes = capture(&m, "function_attr")?;
372 let returns_declaration = capture_opt(&m, "function_returns_declaration")?;
373 let returns = capture_opt(&m, "function_returns")?;
374
375 let span_start = find_definition_start(&func);
376 let span_end = returns_declaration
377 .as_ref()
378 .map_or_else(|| attributes.text_range().end.into(), find_definition_end);
379 let span = span_start..span_end;
380 let name = INTERNER.get_or_intern(name.node().unparse().trim());
381 let params = extract_params(¶ms, NonterminalKind::Parameter);
382 let returns = returns
383 .map(|r| extract_params(&r, NonterminalKind::Parameter))
384 .unwrap_or_default();
385 let natspec = extract_comment(&func.clone(), &returns)?;
386 let parent = extract_parent_name(func);
387
388 Ok(FunctionDefinition {
389 parent,
390 name,
391 span,
392 params,
393 returns,
394 natspec,
395 attributes: extract_attributes(&attributes),
396 }
397 .into())
398 }
399}
400
401impl Extract for ModifierDefinition {
402 fn query() -> Query {
403 Query::create(
404 "@modifier [ModifierDefinition
405 @modifier_name name:[Identifier]
406 parameters:[ParametersDeclaration
407 @modifier_params parameters:[Parameters]
408 ]?
409 @modifier_attr attributes:[ModifierAttributes]
410 ]",
411 )
412 .or_panic("query should compile")
413 }
414
415 fn extract(m: QueryMatch) -> Result<Definition> {
416 let modifier = capture(&m, "modifier")?;
417 let name = capture(&m, "modifier_name")?;
418 let params = capture_opt(&m, "modifier_params")?;
419 let attr = capture(&m, "modifier_attr")?;
420
421 let span_start = find_definition_start(&modifier);
422 let span_end = attr.text_range().end.into();
423 let span = span_start..span_end;
424 let name = INTERNER.get_or_intern(name.node().unparse().trim());
425 let params = params
426 .map(|p| extract_params(&p, NonterminalKind::Parameter))
427 .unwrap_or_default();
428
429 let natspec = extract_comment(&modifier.clone(), &[])?;
430 let parent = extract_parent_name(modifier);
431
432 Ok(ModifierDefinition {
433 parent,
434 name,
435 span,
436 params,
437 natspec,
438 attributes: extract_attributes(&attr),
439 }
440 .into())
441 }
442}
443
444impl Extract for StructDefinition {
445 fn query() -> Query {
446 Query::create(
447 "@struct [StructDefinition
448 @struct_name name:[Identifier]
449 @struct_members members:[StructMembers]
450 ]",
451 )
452 .or_panic("query should compile")
453 }
454
455 fn extract(m: QueryMatch) -> Result<Definition> {
456 let structure = capture(&m, "struct")?;
457 let name = capture(&m, "struct_name")?;
458 let members = capture(&m, "struct_members")?;
459
460 let span = find_definition_start(&structure)..find_definition_end(&structure);
461 let name = INTERNER.get_or_intern(name.node().unparse().trim());
462 let members = extract_struct_members(&members)?;
463 let natspec = extract_comment(&structure.clone(), &[])?;
464 let parent = extract_parent_name(structure);
465
466 Ok(StructDefinition {
467 parent,
468 name,
469 span,
470 members,
471 natspec,
472 }
473 .into())
474 }
475}
476
477impl Extract for VariableDeclaration {
478 fn query() -> Query {
479 Query::create(
480 "@variable [StateVariableDefinition
481 @variable_attr attributes:[StateVariableAttributes]
482 @variable_name name:[Identifier]
483 ]",
484 )
485 .or_panic("query should compile")
486 }
487
488 fn extract(m: QueryMatch) -> Result<Definition> {
489 let variable = capture(&m, "variable")?;
490 let attributes = capture(&m, "variable_attr")?;
491 let name = capture(&m, "variable_name")?;
492
493 let span = find_definition_start(&variable)..find_definition_end(&variable);
494 let name = INTERNER.get_or_intern(name.node().unparse().trim());
495 let natspec = extract_comment(&variable.clone(), &[])?;
496 let parent = extract_parent_name(variable);
497
498 Ok(VariableDeclaration {
499 parent,
500 name,
501 span,
502 natspec,
503 attributes: extract_attributes(&attributes),
504 }
505 .into())
506 }
507}
508
509impl Extract for ContractDefinition {
510 fn query() -> Query {
511 Query::create(
512 "@contract [ContractDefinition
513 @contract_name name:[Identifier]
514 @contract_spec inheritance:[InheritanceSpecifier]?
515 ]",
516 )
517 .or_panic("query should compile")
518 }
519
520 fn extract(m: QueryMatch) -> Result<Definition> {
521 let contract = capture(&m, "contract")?;
522 let name = capture(&m, "contract_name")?;
523 let spec = capture_opt(&m, "contract_spec")?;
524
525 let span_start = find_definition_start(&contract);
526 let span_end = spec
527 .as_ref()
528 .map_or_else(|| name.text_range().end.into(), find_definition_end);
529 let span = span_start..span_end;
530 let name = INTERNER.get_or_intern(name.node().unparse().trim());
531 let natspec = extract_comment(&contract.clone(), &[])?;
532
533 Ok(ContractDefinition {
534 name,
535 span,
536 natspec,
537 }
538 .into())
539 }
540}
541
542impl Extract for InterfaceDefinition {
543 fn query() -> Query {
544 Query::create(
545 "@iface [InterfaceDefinition
546 @iface_name name:[Identifier]
547 @iface_spec inheritance:[InheritanceSpecifier]?
548 ]",
549 )
550 .or_panic("query should compile")
551 }
552
553 fn extract(m: QueryMatch) -> Result<Definition> {
554 let iface = capture(&m, "iface")?;
555 let name = capture(&m, "iface_name")?;
556 let spec = capture_opt(&m, "iface_spec")?;
557
558 let span_start = find_definition_start(&iface);
559 let span_end = spec
560 .as_ref()
561 .map_or_else(|| name.text_range().end.into(), find_definition_end);
562 let span = span_start..span_end;
563 let name = INTERNER.get_or_intern(name.node().unparse().trim());
564 let natspec = extract_comment(&iface.clone(), &[])?;
565
566 Ok(InterfaceDefinition {
567 name,
568 span,
569 natspec,
570 }
571 .into())
572 }
573}
574
575impl Extract for LibraryDefinition {
576 fn query() -> Query {
577 Query::create(
578 "@library [LibraryDefinition
579 @library_name name:[Identifier]
580 ]",
581 )
582 .or_panic("query should compile")
583 }
584
585 fn extract(m: QueryMatch) -> Result<Definition> {
586 let library = capture(&m, "library")?;
587 let name = capture(&m, "library_name")?;
588
589 let span = find_definition_start(&library)..name.text_range().end.into();
590 let name = INTERNER.get_or_intern(name.node().unparse().trim());
591 let natspec = extract_comment(&library.clone(), &[])?;
592
593 Ok(LibraryDefinition {
594 name,
595 span,
596 natspec,
597 }
598 .into())
599 }
600}
601
602pub fn capture(m: &QueryMatch, name: &str) -> Result<Cursor> {
604 match m
605 .capture(name)
606 .map(|capture| capture.cursors().first().cloned())
607 {
608 Some(Some(res)) => Ok(res),
609 _ => Err(ErrorKind::UnknownError.into()),
610 }
611}
612
613pub fn capture_opt(m: &QueryMatch, name: &str) -> Result<Option<Cursor>> {
615 match m
616 .capture(name)
617 .map(|capture| capture.cursors().first().cloned())
618 {
619 Some(Some(res)) => Ok(Some(res)),
620 Some(None) => Ok(None),
621 _ => Err(ErrorKind::UnknownError.into()),
622 }
623}
624
625#[must_use]
629pub fn extract_params(cursor: &Cursor, kind: NonterminalKind) -> Vec<Identifier> {
630 let mut cursor = cursor.spawn();
631 let mut out = Vec::new();
632 while cursor.go_to_next_nonterminal_with_kind(kind) {
633 let mut sub_cursor = cursor.spawn();
634 let mut found = false;
635 while sub_cursor.go_to_next_terminal_with_kind(TerminalKind::Identifier) {
636 if sub_cursor.label().to_string() != "name" {
637 continue;
638 }
639 found = true;
640 out.push(Identifier {
641 name: Some(INTERNER.get_or_intern(sub_cursor.node().unparse().trim())),
642 span: textrange(sub_cursor.text_range()),
643 });
644 }
645 if !found {
646 out.push(Identifier {
647 name: None,
648 span: find_definition_start(&cursor)..find_definition_end(&cursor),
649 });
650 }
651 }
652 out
653}
654
655pub fn extract_comment(cursor: &Cursor, returns: &[Identifier]) -> Result<Option<NatSpec>> {
657 let mut cursor = cursor.spawn();
658 let mut items = Vec::new();
659 while cursor.go_to_next() {
660 if cursor.node().is_terminal_with_kinds(&[
661 TerminalKind::MultiLineNatSpecComment,
662 TerminalKind::SingleLineNatSpecComment,
663 ]) {
664 let comment = &cursor.node().unparse();
665 let mut trimmed = comment.trim_start();
666 if trimmed.starts_with("////") || trimmed.starts_with("/***") {
667 continue;
669 }
670 items.push((
671 cursor.node().kind().to_string(), cursor.text_range().start.line, parse_comment(&mut trimmed)
674 .map_err(|e| ErrorKind::NatspecParsingError {
675 parent: extract_parent_name(cursor.clone()),
676 span: textrange(cursor.text_range()),
677 message: e.to_string(),
678 })?
679 .populate_returns(returns),
680 ));
681 } else if cursor.node().is_terminal_with_kinds(&[
682 TerminalKind::ContractKeyword,
683 TerminalKind::InterfaceKeyword,
684 TerminalKind::LibraryKeyword,
685 TerminalKind::ConstructorKeyword,
686 TerminalKind::EnumKeyword,
687 TerminalKind::ErrorKeyword,
688 TerminalKind::EventKeyword,
689 TerminalKind::FunctionKeyword,
690 TerminalKind::ModifierKeyword,
691 TerminalKind::StructKeyword,
692 ]) | cursor
693 .node()
694 .is_nonterminal_with_kind(NonterminalKind::StateVariableAttributes)
695 {
696 break;
698 }
699 }
700 if let Some("MultiLineNatSpecComment") = items.last().map(|(kind, _, _)| kind.as_str())
701 && let Some((_, _, natspec)) = items.pop()
702 {
703 return Ok(Some(natspec));
705 }
706 let mut res = Vec::new();
709 let mut iter = items.into_iter().rev().peekable();
710 while let Some((_, item_line, item)) = iter.next() {
711 res.push(item);
712 if let Some((next_kind, next_line, _)) = iter.peek()
713 && (next_kind == "MultiLineNatSpecComment" || *next_line < item_line - 1)
714 {
715 break;
717 }
718 }
719 if res.is_empty() {
720 return Ok(None);
721 }
722 Ok(Some(res.into_iter().rev().fold(
723 NatSpec::default(),
724 |mut acc, mut i| {
725 acc.append(&mut i);
726 acc
727 },
728 )))
729}
730
731#[must_use]
733pub fn extract_identifiers(cursor: &Cursor) -> Vec<Identifier> {
734 let mut cursor = cursor.spawn();
735 let mut out = Vec::new();
736 while cursor.go_to_next_terminal_with_kind(TerminalKind::Identifier) {
737 if cursor.label().to_string() != "name" {
738 continue;
739 }
740 out.push(Identifier {
741 name: Some(INTERNER.get_or_intern(cursor.node().unparse().trim())),
742 span: textrange(cursor.text_range()),
743 });
744 }
745 out
746}
747
748#[must_use]
750pub fn extract_attributes(cursor: &Cursor) -> Attributes {
751 let mut cursor = cursor.spawn();
752 let mut out = Attributes::default();
753 while cursor.go_to_next_terminal_with_kinds(&[
754 TerminalKind::ExternalKeyword,
755 TerminalKind::InternalKeyword,
756 TerminalKind::PrivateKeyword,
757 TerminalKind::PublicKeyword,
758 TerminalKind::OverrideKeyword,
759 ]) {
760 match cursor
761 .node()
762 .as_terminal()
763 .or_panic("should be terminal kind")
764 .kind
765 {
766 TerminalKind::ExternalKeyword => out.visibility = Visibility::External,
767 TerminalKind::InternalKeyword => out.visibility = Visibility::Internal,
768 TerminalKind::PrivateKeyword => out.visibility = Visibility::Private,
769 TerminalKind::PublicKeyword => out.visibility = Visibility::Public,
770 TerminalKind::OverrideKeyword => out.r#override = true,
771 _ => unreachable!(),
772 }
773 }
774 out
775}
776
777#[must_use]
779pub fn extract_parent_name(mut cursor: Cursor) -> Option<Parent> {
780 while cursor.go_to_parent() {
781 if let Some(parent) = cursor.node().as_nonterminal_with_kinds(&[
782 NonterminalKind::ContractDefinition,
783 NonterminalKind::InterfaceDefinition,
784 NonterminalKind::LibraryDefinition,
785 ]) {
786 for child in &parent.children {
787 if child.is_terminal_with_kind(TerminalKind::Identifier) {
788 let name = INTERNER
789 .get_or_intern(child.node.unparse().trim())
790 .resolve_with(&INTERNER);
791 return Some(match parent.kind {
792 NonterminalKind::ContractDefinition => Parent::Contract(name),
793 NonterminalKind::InterfaceDefinition => Parent::Interface(name),
794 NonterminalKind::LibraryDefinition => Parent::Library(name),
795 _ => unreachable!(),
796 });
797 }
798 }
799 }
800 }
801 None
802}
803
804#[must_use]
806pub fn extract_enum_members(cursor: &Cursor) -> Vec<Identifier> {
807 let mut cursor = cursor.spawn();
808 let mut out = Vec::new();
809 while cursor.go_to_next_terminal_with_kind(TerminalKind::Identifier) {
810 out.push(Identifier {
811 name: Some(INTERNER.get_or_intern(cursor.node().unparse().trim())),
812 span: textrange(cursor.text_range()),
813 });
814 }
815 out
816}
817
818pub fn extract_struct_members(cursor: &Cursor) -> Result<Vec<Identifier>> {
820 let cursor = cursor.spawn();
821 let mut out = Vec::new();
822 let query = Query::create(
823 "[StructMember
824 @member_name name:[Identifier]
825 ]",
826 )
827 .or_panic("query should compile");
828 for m in cursor.query(vec![query]) {
829 let member_name = capture(&m, "member_name")?;
830 out.push(Identifier {
831 name: Some(INTERNER.get_or_intern(member_name.node().unparse().trim())),
832 span: textrange(member_name.text_range()),
833 });
834 }
835 Ok(out)
836}
837
838#[must_use]
840pub fn find_definition_end(cursor: &Cursor) -> TextIndex {
841 let default = cursor.text_range().end.into();
842 let mut cursor = cursor.spawn();
843 if !cursor.go_to_last_child() {
844 return default;
845 }
846 if !cursor.node().is_trivia() {
847 return cursor.text_range().end.into();
848 }
849 while cursor.go_to_previous() {
850 if cursor.node().is_trivia() {
851 continue;
852 }
853 return cursor.text_range().end.into();
854 }
855 default
856}
857
858#[must_use]
860pub fn find_definition_start(cursor: &Cursor) -> TextIndex {
861 let default = cursor.text_range().start.into();
862 let mut cursor = cursor.spawn();
863 while cursor.go_to_next() {
864 if cursor.node().is_terminal_with_kinds(&[
865 TerminalKind::Whitespace,
866 TerminalKind::EndOfLine,
867 TerminalKind::SingleLineComment,
868 TerminalKind::MultiLineComment,
869 ]) {
870 continue;
871 }
872 if cursor.node().is_nonterminal_with_kinds(&[
874 NonterminalKind::TypeName,
875 NonterminalKind::ElementaryType,
876 NonterminalKind::IdentifierPath, ]) {
878 continue;
879 }
880 if cursor.node().is_terminal_with_kinds(&[
881 TerminalKind::SingleLineNatSpecComment,
882 TerminalKind::MultiLineNatSpecComment,
883 ]) {
884 let comment = cursor.node().unparse();
885 let comment = comment.trim_start();
886 if comment.starts_with("////") || comment.starts_with("/***") {
887 continue;
889 }
890 }
891 return cursor.text_range().start.into();
892 }
893 default
894}
895
896#[must_use]
898pub fn textrange(value: SlangTextRange) -> TextRange {
899 value.start.into()..value.end.into()
900}
901
902impl From<SlangTextIndex> for TextIndex {
903 fn from(value: SlangTextIndex) -> Self {
904 Self {
905 utf8: value.utf8,
906 line: u32::try_from(value.line).or_panic("slang line number is too large for u32"),
907 col_utf32: u32::try_from(value.column)
908 .or_panic("slang column number is too large for u32"),
909 col_utf8: 0,
911 col_utf16: 0,
912 }
913 }
914}
915
916#[cfg(test)]
917mod tests {
918 use std::{fs::File, ops::Range};
919
920 use similar_asserts::assert_eq;
921 use slang_solidity::{cst::Cursor, parser::Parser};
922
923 use crate::{
924 interner::INTERNER,
925 natspec::{NatSpecItem, NatSpecKind},
926 utils::detect_solidity_version,
927 };
928
929 use super::*;
930
931 fn parse_file(contents: &str) -> Cursor {
932 let solidity_version = detect_solidity_version(contents, PathBuf::new()).unwrap();
933 let parser = Parser::create(solidity_version).unwrap();
934 let output = parser.parse_file_contents(contents);
935 assert!(output.is_valid(), "{:?}", output.errors());
936 output.create_tree_cursor()
937 }
938
939 macro_rules! impl_find_contract_item {
940 ($fn_name:ident, $item_variant:path, $item_type:ty) => {
941 fn $fn_name<'a>(name: &str, items: &'a [Definition]) -> &'a $item_type {
942 items
943 .iter()
944 .find_map(|d| match d {
945 $item_variant(def) if def.name == INTERNER.get_or_intern(name) => Some(def),
946 _ => None,
947 })
948 .unwrap()
949 }
950 };
951 }
952 macro_rules! impl_find_item {
953 ($fn_name:ident, $item_variant:path, $item_type:ty) => {
954 fn $fn_name<'a>(
955 name: &str,
956 parent: Option<Parent>,
957 items: &'a [Definition],
958 ) -> &'a $item_type {
959 items
960 .iter()
961 .find_map(|d| match d {
962 $item_variant(def)
963 if def.name == INTERNER.get_or_intern(name) && def.parent == parent =>
964 {
965 Some(def)
966 }
967 _ => None,
968 })
969 .unwrap()
970 }
971 };
972 }
973
974 impl_find_contract_item!(find_contract, Definition::Contract, ContractDefinition);
975 impl_find_contract_item!(find_interface, Definition::Interface, InterfaceDefinition);
976 impl_find_contract_item!(find_library, Definition::Library, LibraryDefinition);
977 impl_find_item!(find_function, Definition::Function, FunctionDefinition);
978 impl_find_item!(find_variable, Definition::Variable, VariableDeclaration);
979 impl_find_item!(find_modifier, Definition::Modifier, ModifierDefinition);
980 impl_find_item!(find_error, Definition::Error, ErrorDefinition);
981 impl_find_item!(find_event, Definition::Event, EventDefinition);
982 impl_find_item!(find_struct, Definition::Struct, StructDefinition);
983 impl_find_item!(find_enum, Definition::Enumeration, EnumDefinition);
984
985 macro_rules! adjust_offset_windows {
986 ($byte_index:literal, $lines:literal) => {
987 if cfg!(windows) {
988 $byte_index + $lines
989 } else {
990 $byte_index
991 }
992 };
993 }
994
995 #[expect(clippy::cast_possible_truncation)]
996 fn single_line_textrange(range: Range<usize>) -> TextRange {
997 TextIndex {
998 utf8: range.start,
999 line: 0,
1000 col_utf8: range.start as u32,
1001 col_utf16: range.start as u32,
1002 col_utf32: range.start as u32,
1003 }..TextIndex {
1004 utf8: range.end,
1005 line: 0,
1006 col_utf8: range.end as u32,
1007 col_utf16: range.end as u32,
1008 col_utf32: range.end as u32,
1009 }
1010 }
1011
1012 #[test]
1013 fn test_parse_contract() {
1014 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1015 let items = SlangParser::find_items(cursor);
1016 let item = find_contract("ParserTest", &items);
1017 assert_eq!(
1018 item.natspec.as_ref().unwrap().items,
1019 vec![NatSpecItem {
1020 kind: NatSpecKind::Notice,
1021 comment: "A contract with correct natspec".to_string(),
1022 span: single_line_textrange(4..43)
1023 }]
1024 );
1025 }
1026
1027 #[test]
1028 fn test_parse_interface() {
1029 let cursor = parse_file(include_str!("../../test-data/InterfaceSample.sol"));
1030 let items = SlangParser::find_items(cursor);
1031 let item = find_interface("IInterfacedSample", &items);
1032 assert_eq!(
1033 item.natspec.as_ref().unwrap().items,
1034 vec![NatSpecItem {
1035 kind: NatSpecKind::Title,
1036 comment: "The interface".to_string(),
1037 span: single_line_textrange(4..24)
1038 }]
1039 );
1040 }
1041
1042 #[test]
1043 fn test_parse_library() {
1044 let cursor = parse_file(include_str!("../../test-data/LibrarySample.sol"));
1045 let items = SlangParser::find_items(cursor);
1046 let item = find_library("StringUtils", &items);
1047 assert_eq!(
1048 item.natspec.as_ref().unwrap().items,
1049 vec![NatSpecItem {
1050 kind: NatSpecKind::Title,
1051 comment: "StringUtils".to_string(),
1052 span: single_line_textrange(4..22)
1053 }]
1054 );
1055 }
1056
1057 #[test]
1058 fn test_parse_external_function() {
1059 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1060 let items = SlangParser::find_items(cursor);
1061 let item = find_function(
1062 "viewFunctionNoParams",
1063 Some(Parent::Contract("ParserTest")),
1064 &items,
1065 );
1066 assert_eq!(
1067 item.natspec.as_ref().unwrap().items,
1068 vec![
1069 NatSpecItem {
1070 kind: NatSpecKind::Inheritdoc {
1071 parent: INTERNER.get_or_intern("IParserTest")
1072 },
1073 comment: String::new(),
1074 span: single_line_textrange(4..27)
1075 },
1076 NatSpecItem {
1077 kind: NatSpecKind::Dev,
1078 comment: "Dev comment for the function".to_string(),
1079 span: single_line_textrange(4..37)
1080 }
1081 ]
1082 );
1083 }
1084
1085 #[test]
1086 fn test_parse_constant() {
1087 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1088 let items = SlangParser::find_items(cursor);
1089 let item = find_variable(
1090 "SOME_CONSTANT",
1091 Some(Parent::Contract("ParserTest")),
1092 &items,
1093 );
1094 assert_eq!(
1095 item.natspec.as_ref().unwrap().items,
1096 vec![NatSpecItem {
1097 kind: NatSpecKind::Inheritdoc {
1098 parent: INTERNER.get_or_intern("IParserTest")
1099 },
1100 comment: String::new(),
1101 span: single_line_textrange(4..27)
1102 },]
1103 );
1104 }
1105
1106 #[test]
1107 fn test_parse_variable() {
1108 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1109 let items = SlangParser::find_items(cursor);
1110 let item = find_variable("someVariable", Some(Parent::Contract("ParserTest")), &items);
1111 assert_eq!(
1112 item.natspec.as_ref().unwrap().items,
1113 vec![NatSpecItem {
1114 kind: NatSpecKind::Inheritdoc {
1115 parent: INTERNER.get_or_intern("IParserTest")
1116 },
1117 comment: String::new(),
1118 span: single_line_textrange(4..27)
1119 },]
1120 );
1121 }
1122
1123 #[test]
1124 fn test_parse_modifier() {
1125 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1126 let items = SlangParser::find_items(cursor);
1127 let item = find_modifier("someModifier", Some(Parent::Contract("ParserTest")), &items);
1128 assert_eq!(
1129 item.natspec.as_ref().unwrap().items,
1130 vec![
1131 NatSpecItem {
1132 kind: NatSpecKind::Notice,
1133 comment: "The description of the modifier".to_string(),
1134 span: single_line_textrange(4..43)
1135 },
1136 NatSpecItem {
1137 kind: NatSpecKind::Param {
1138 name: INTERNER.get_or_intern("_param1")
1139 },
1140 comment: "The only parameter".to_string(),
1141 span: single_line_textrange(4..37)
1142 },
1143 ]
1144 );
1145 }
1146
1147 #[test]
1148 fn test_parse_modifier_no_param() {
1149 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1150 let items = SlangParser::find_items(cursor);
1151 let item = find_modifier(
1152 "modifierWithoutParam",
1153 Some(Parent::Contract("ParserTest")),
1154 &items,
1155 );
1156 assert_eq!(
1157 item.natspec.as_ref().unwrap().items,
1158 vec![NatSpecItem {
1159 kind: NatSpecKind::Notice,
1160 comment: "The description of the modifier".to_string(),
1161 span: single_line_textrange(4..43)
1162 },]
1163 );
1164 }
1165
1166 #[test]
1167 fn test_parse_private_function() {
1168 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1169 let items = SlangParser::find_items(cursor);
1170 let item = find_function("_viewPrivate", Some(Parent::Contract("ParserTest")), &items);
1171 assert_eq!(
1172 item.natspec.as_ref().unwrap().items,
1173 vec![
1174 NatSpecItem {
1175 kind: NatSpecKind::Notice,
1176 comment: "Some private stuff".to_string(),
1177 span: single_line_textrange(4..30)
1178 },
1179 NatSpecItem {
1180 kind: NatSpecKind::Dev,
1181 comment: "Dev comment for the private function".to_string(),
1182 span: single_line_textrange(4..45)
1183 },
1184 NatSpecItem {
1185 kind: NatSpecKind::Param {
1186 name: INTERNER.get_or_intern("_paramName")
1187 },
1188 comment: "The parameter name".to_string(),
1189 span: single_line_textrange(4..40)
1190 },
1191 NatSpecItem {
1192 kind: NatSpecKind::Return {
1193 name: Some(INTERNER.get_or_intern("_returned"))
1194 },
1195 comment: "The returned value".to_string(),
1196 span: single_line_textrange(4..40)
1197 }
1198 ]
1199 );
1200 }
1201
1202 #[test]
1203 fn test_parse_multiline_descriptions() {
1204 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1205 let items = SlangParser::find_items(cursor);
1206 let item = find_function(
1207 "_viewMultiline",
1208 Some(Parent::Contract("ParserTest")),
1209 &items,
1210 );
1211 assert_eq!(
1212 item.natspec.as_ref().unwrap().items,
1213 vec![
1214 NatSpecItem {
1215 kind: NatSpecKind::Notice,
1216 comment: "Some internal stuff".to_string(),
1217 span: single_line_textrange(4..31)
1218 },
1219 NatSpecItem {
1220 kind: NatSpecKind::Notice,
1221 comment: "Separate line".to_string(),
1222 span: single_line_textrange(12..25)
1223 },
1224 NatSpecItem {
1225 kind: NatSpecKind::Notice,
1226 comment: "Third one".to_string(),
1227 span: single_line_textrange(12..21)
1228 },
1229 ]
1230 );
1231 }
1232
1233 #[test]
1234 fn test_parse_multiple_same_tag() {
1235 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1236 let items = SlangParser::find_items(cursor);
1237 let item = find_function(
1238 "_viewDuplicateTag",
1239 Some(Parent::Contract("ParserTest")),
1240 &items,
1241 );
1242 assert_eq!(
1243 item.natspec.as_ref().unwrap().items,
1244 vec![
1245 NatSpecItem {
1246 kind: NatSpecKind::Notice,
1247 comment: "Some internal stuff".to_string(),
1248 span: single_line_textrange(4..31)
1249 },
1250 NatSpecItem {
1251 kind: NatSpecKind::Notice,
1252 comment: "Separate line".to_string(),
1253 span: single_line_textrange(4..25)
1254 },
1255 ]
1256 );
1257 }
1258
1259 #[test]
1260 fn test_parse_error() {
1261 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1262 let items = SlangParser::find_items(cursor);
1263 let item = find_error(
1264 "SimpleError",
1265 Some(Parent::Interface("IParserTest")),
1266 &items,
1267 );
1268 assert_eq!(
1269 item.natspec.as_ref().unwrap().items,
1270 vec![NatSpecItem {
1271 kind: NatSpecKind::Notice,
1272 comment: "Thrown whenever something goes wrong".to_string(),
1273 span: single_line_textrange(4..48)
1274 },]
1275 );
1276 }
1277
1278 #[test]
1279 fn test_parse_event() {
1280 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1281 let items = SlangParser::find_items(cursor);
1282 let item = find_event(
1283 "SimpleEvent",
1284 Some(Parent::Interface("IParserTest")),
1285 &items,
1286 );
1287 assert_eq!(
1288 item.natspec.as_ref().unwrap().items,
1289 vec![NatSpecItem {
1290 kind: NatSpecKind::Notice,
1291 comment: "Emitted whenever something happens".to_string(),
1292 span: single_line_textrange(4..46)
1293 },]
1294 );
1295 }
1296
1297 #[test]
1298 fn test_parse_struct() {
1299 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1300 let items = SlangParser::find_items(cursor);
1301 let item = find_struct(
1302 "SimplestStruct",
1303 Some(Parent::Interface("IParserTest")),
1304 &items,
1305 );
1306 assert_eq!(
1307 item.natspec.as_ref().unwrap().items,
1308 vec![
1309 NatSpecItem {
1310 kind: NatSpecKind::Notice,
1311 comment: "A struct holding 2 variables of type uint256".to_string(),
1312 span: single_line_textrange(4..56)
1313 },
1314 NatSpecItem {
1315 kind: NatSpecKind::Param {
1316 name: INTERNER.get_or_intern("a")
1317 },
1318 comment: "The first variable".to_string(),
1319 span: single_line_textrange(4..32)
1320 },
1321 NatSpecItem {
1322 kind: NatSpecKind::Param {
1323 name: INTERNER.get_or_intern("b")
1324 },
1325 comment: "The second variable".to_string(),
1326 span: single_line_textrange(4..33)
1327 },
1328 NatSpecItem {
1329 kind: NatSpecKind::Dev,
1330 comment: "This is definitely a struct".to_string(),
1331 span: single_line_textrange(4..36)
1332 },
1333 ]
1334 );
1335 }
1336
1337 #[test]
1338 fn test_parse_external_function_no_params() {
1339 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1340 let items = SlangParser::find_items(cursor);
1341 let item = find_function(
1342 "viewFunctionNoParams",
1343 Some(Parent::Interface("IParserTest")),
1344 &items,
1345 );
1346 assert_eq!(
1347 item.natspec.as_ref().unwrap().items,
1348 vec![
1349 NatSpecItem {
1350 kind: NatSpecKind::Notice,
1351 comment: "View function with no parameters".to_string(),
1352 span: single_line_textrange(4..44)
1353 },
1354 NatSpecItem {
1355 kind: NatSpecKind::Dev,
1356 comment: "Natspec for the return value is missing".to_string(),
1357 span: single_line_textrange(4..48)
1358 },
1359 NatSpecItem {
1360 kind: NatSpecKind::Return { name: None },
1361 comment: "The returned value".to_string(),
1362 span: single_line_textrange(4..30)
1363 },
1364 ]
1365 );
1366 }
1367
1368 #[test]
1369 fn test_parse_external_function_params() {
1370 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1371 let items = SlangParser::find_items(cursor);
1372 let item = find_function(
1373 "viewFunctionWithParams",
1374 Some(Parent::Interface("IParserTest")),
1375 &items,
1376 );
1377 assert_eq!(
1378 item.natspec.as_ref().unwrap().items,
1379 vec![
1380 NatSpecItem {
1381 kind: NatSpecKind::Notice,
1382 comment: "A function with different style of natspec".to_string(),
1383 span: TextIndex {
1384 utf8: adjust_offset_windows!(9, 1),
1385 line: 1,
1386 col_utf8: 5,
1387 col_utf16: 5,
1388 col_utf32: 5,
1389 }..TextIndex {
1390 utf8: adjust_offset_windows!(59, 1),
1391 line: 1,
1392 col_utf8: 55,
1393 col_utf16: 55,
1394 col_utf32: 55,
1395 }
1396 },
1397 NatSpecItem {
1398 kind: NatSpecKind::Param {
1399 name: INTERNER.get_or_intern("_param1")
1400 },
1401 comment: "The first parameter".to_string(),
1402 span: TextIndex {
1403 utf8: adjust_offset_windows!(65, 2),
1404 line: 2,
1405 col_utf8: 5,
1406 col_utf16: 5,
1407 col_utf32: 5,
1408 }..TextIndex {
1409 utf8: adjust_offset_windows!(100, 2),
1410 line: 2,
1411 col_utf8: 40,
1412 col_utf16: 40,
1413 col_utf32: 40,
1414 }
1415 },
1416 NatSpecItem {
1417 kind: NatSpecKind::Param {
1418 name: INTERNER.get_or_intern("_param2")
1419 },
1420 comment: "The second parameter".to_string(),
1421 span: TextIndex {
1422 utf8: adjust_offset_windows!(106, 3),
1423 line: 3,
1424 col_utf8: 5,
1425 col_utf16: 5,
1426 col_utf32: 5,
1427 }..TextIndex {
1428 utf8: adjust_offset_windows!(142, 3),
1429 line: 3,
1430 col_utf8: 41,
1431 col_utf16: 41,
1432 col_utf32: 41,
1433 }
1434 },
1435 NatSpecItem {
1436 kind: NatSpecKind::Return { name: None },
1437 comment: "The returned value".to_string(),
1438 span: TextIndex {
1439 utf8: adjust_offset_windows!(148, 4),
1440 line: 4,
1441 col_utf8: 5,
1442 col_utf16: 5,
1443 col_utf32: 5,
1444 }..TextIndex {
1445 utf8: adjust_offset_windows!(174, 4),
1446 line: 4,
1447 col_utf8: 31,
1448 col_utf16: 31,
1449 col_utf32: 31,
1450 }
1451 },
1452 ]
1453 );
1454 }
1455
1456 #[test]
1457 fn test_parse_funny_struct() {
1458 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1459 let items = SlangParser::find_items(cursor);
1460 let item = find_struct(
1461 "SimpleStruct",
1462 Some(Parent::Contract("ParserTestFunny")),
1463 &items,
1464 );
1465 assert_eq!(item.natspec, None);
1466 }
1467
1468 #[test]
1469 fn test_parse_funny_variable() {
1470 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1471 let items = SlangParser::find_items(cursor);
1472 let item = find_variable(
1473 "someVariable",
1474 Some(Parent::Contract("ParserTestFunny")),
1475 &items,
1476 );
1477 assert_eq!(
1478 item.natspec.as_ref().unwrap().items,
1479 vec![
1480 NatSpecItem {
1481 kind: NatSpecKind::Inheritdoc {
1482 parent: INTERNER.get_or_intern("IParserTest")
1483 },
1484 comment: String::new(),
1485 span: single_line_textrange(4..27)
1486 },
1487 NatSpecItem {
1488 kind: NatSpecKind::Dev,
1489 comment: "Providing context".to_string(),
1490 span: single_line_textrange(4..26)
1491 }
1492 ]
1493 );
1494 }
1495
1496 #[test]
1497 fn test_parse_funny_constant() {
1498 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1499 let items = SlangParser::find_items(cursor);
1500 let item = find_variable(
1501 "SOME_CONSTANT",
1502 Some(Parent::Contract("ParserTestFunny")),
1503 &items,
1504 );
1505 assert_eq!(item.natspec, None);
1506 }
1507
1508 #[test]
1509 fn test_parse_funny_function_params() {
1510 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1511 let items = SlangParser::find_items(cursor);
1512 let item = find_function(
1513 "viewFunctionWithParams",
1514 Some(Parent::Contract("ParserTestFunny")),
1515 &items,
1516 );
1517 assert_eq!(item.natspec, None);
1518 }
1519
1520 #[test]
1521 fn test_parse_funny_function_private() {
1522 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1523 let items = SlangParser::find_items(cursor);
1524 let item = find_function(
1525 "_viewPrivateMulti",
1526 Some(Parent::Contract("ParserTestFunny")),
1527 &items,
1528 );
1529 assert_eq!(
1530 item.natspec.as_ref().unwrap().items,
1531 vec![
1532 NatSpecItem {
1533 kind: NatSpecKind::Notice,
1534 comment: "Some private stuff".to_string(),
1535 span: TextIndex {
1536 utf8: adjust_offset_windows!(9, 1),
1537 line: 1,
1538 col_utf8: 5,
1539 col_utf16: 5,
1540 col_utf32: 5,
1541 }..TextIndex {
1542 utf8: adjust_offset_windows!(37, 1),
1543 line: 1,
1544 col_utf8: 33,
1545 col_utf16: 33,
1546 col_utf32: 33,
1547 }
1548 },
1549 NatSpecItem {
1550 kind: NatSpecKind::Param {
1551 name: INTERNER.get_or_intern("_paramName")
1552 },
1553 comment: "The parameter name".to_string(),
1554 span: TextIndex {
1555 utf8: adjust_offset_windows!(43, 2),
1556 line: 2,
1557 col_utf8: 5,
1558 col_utf16: 5,
1559 col_utf32: 5,
1560 }..TextIndex {
1561 utf8: adjust_offset_windows!(84, 2),
1562 line: 2,
1563 col_utf8: 46,
1564 col_utf16: 46,
1565 col_utf32: 46,
1566 }
1567 },
1568 NatSpecItem {
1569 kind: NatSpecKind::Return {
1570 name: Some(INTERNER.get_or_intern("_returned"))
1571 },
1572 comment: "The returned value".to_string(),
1573 span: TextIndex {
1574 utf8: adjust_offset_windows!(90, 3),
1575 line: 3,
1576 col_utf8: 5,
1577 col_utf16: 5,
1578 col_utf32: 5,
1579 }..TextIndex {
1580 utf8: adjust_offset_windows!(134, 3),
1581 line: 3,
1582 col_utf8: 49,
1583 col_utf16: 49,
1584 col_utf32: 49,
1585 }
1586 },
1587 ]
1588 );
1589 }
1590
1591 #[test]
1592 fn test_parse_funny_function_private_single() {
1593 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1594 let items = SlangParser::find_items(cursor);
1595 let item = find_function(
1596 "_viewPrivateSingle",
1597 Some(Parent::Contract("ParserTestFunny")),
1598 &items,
1599 );
1600 assert_eq!(
1601 item.natspec.as_ref().unwrap().items,
1602 vec![
1603 NatSpecItem {
1604 kind: NatSpecKind::Notice,
1605 comment: "Some private stuff".to_string(),
1606 span: single_line_textrange(4..32)
1607 },
1608 NatSpecItem {
1609 kind: NatSpecKind::Param {
1610 name: INTERNER.get_or_intern("_paramName")
1611 },
1612 comment: "The parameter name".to_string(),
1613 span: single_line_textrange(4..45)
1614 },
1615 NatSpecItem {
1616 kind: NatSpecKind::Return {
1617 name: Some(INTERNER.get_or_intern("_returned"))
1618 },
1619 comment: "The returned value".to_string(),
1620 span: single_line_textrange(4..48)
1621 },
1622 ]
1623 );
1624 }
1625
1626 #[test]
1627 fn test_parse_funny_internal() {
1628 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1629 let items = SlangParser::find_items(cursor);
1630 let item = find_function(
1631 "_viewInternal",
1632 Some(Parent::Contract("ParserTestFunny")),
1633 &items,
1634 );
1635 assert_eq!(item.natspec, None);
1636 }
1637
1638 #[test]
1639 fn test_parse_funny_linter_fail() {
1640 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1641 let items = SlangParser::find_items(cursor);
1642 let item = find_function(
1643 "_viewLinterFail",
1644 Some(Parent::Contract("ParserTestFunny")),
1645 &items,
1646 );
1647 assert_eq!(
1648 item.natspec.as_ref().unwrap().items,
1649 vec![
1650 NatSpecItem {
1651 kind: NatSpecKind::Notice,
1652 comment: "Linter fail".to_string(),
1653 span: single_line_textrange(4..23)
1654 },
1655 NatSpecItem {
1656 kind: NatSpecKind::Dev,
1657 comment: "What have I done".to_string(),
1658 span: single_line_textrange(4..30)
1659 }
1660 ]
1661 );
1662 }
1663
1664 #[test]
1665 fn test_parse_funny_empty_return() {
1666 let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
1667 let items = SlangParser::find_items(cursor);
1668 let item = find_function(
1669 "functionUnnamedEmptyReturn",
1670 Some(Parent::Contract("ParserTestFunny")),
1671 &items,
1672 );
1673 assert_eq!(
1674 item.natspec.as_ref().unwrap().items,
1675 vec![
1676 NatSpecItem {
1677 kind: NatSpecKind::Notice,
1678 comment: "fun fact: there are extra spaces after the 1st return".to_string(),
1679 span: single_line_textrange(4..57)
1680 },
1681 NatSpecItem {
1682 kind: NatSpecKind::Return { name: None },
1683 comment: String::new(),
1684 span: single_line_textrange(4..18)
1685 },
1686 NatSpecItem {
1687 kind: NatSpecKind::Return { name: None },
1688 comment: String::new(),
1689 span: single_line_textrange(4..11)
1690 },
1691 ]
1692 );
1693 }
1694
1695 #[test]
1696 fn test_parse_solidity_latest() {
1697 let contents = include_str!("../../test-data/LatestVersion.sol");
1698 let solidity_version = detect_solidity_version(contents, PathBuf::new()).unwrap();
1699 let parser = Parser::create(solidity_version).unwrap();
1700 let output = parser.parse_file_contents(contents);
1701 assert!(output.is_valid(), "{:?}", output.errors());
1702 }
1703
1704 #[test]
1705 fn test_parse_solidity_unsupported() {
1706 let mut parser = SlangParser::builder().skip_version_detection(true).build();
1707 let file = File::open("test-data/UnsupportedVersion.sol").unwrap();
1708 let output = parser.parse_document(file, None::<PathBuf>, false);
1709 assert!(output.is_ok(), "{output:?}");
1710 }
1711
1712 #[test]
1713 fn test_parse_unicode() {
1714 let cursor = parse_file(include_str!("../../test-data/UnicodeSample.sol"));
1715 let items = SlangParser::find_items(cursor);
1716 let item = find_enum("TestEnum", Some(Parent::Contract("UnicodeSample")), &items);
1717 assert_eq!(
1718 item.natspec.as_ref().unwrap().items,
1719 vec![NatSpecItem {
1720 kind: NatSpecKind::Notice,
1721 comment: "An enum 👨🏾👩🏾👧🏾".to_string(),
1722 span: TextIndex {
1723 utf8: 4,
1724 line: 0,
1725 col_utf8: 4,
1726 col_utf16: 4,
1727 col_utf32: 4,
1728 }..TextIndex {
1729 utf8: 50,
1730 line: 0,
1731 col_utf8: 50,
1732 col_utf16: 34,
1733 col_utf32: 28,
1734 },
1735 }]
1736 );
1737 }
1738}