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