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