@@ -165,7 +165,13 @@ pub enum Definition {
165
165
}
166
166
167
167
impl PartialEq for Definition {
168
+ /// Compare two definitions based on their underlying node
169
+ ///
170
+ /// If two instances of the same node generate two definitions (due to quantifiers in the query), this can be
171
+ /// used to deduplicate the instances.
168
172
fn eq ( & self , other : & Self ) -> bool {
173
+ // TODO: is the usage of the span start fine here, or should we instead rely on the `id()` of the node? It would
174
+ // require adding a field in the definition just for that.
169
175
match ( self , other) {
170
176
( Self :: Constructor ( a) , Self :: Constructor ( b) ) => a. span . start == b. span . start ,
171
177
( Self :: Enumeration ( a) , Self :: Enumeration ( b) ) => a. span . start == b. span . start ,
@@ -362,30 +368,70 @@ pub fn extract_params(cursor: Cursor, kind: NonterminalKind) -> Vec<Identifier>
362
368
363
369
/// Extract and parse the NatSpec comment information, if any
364
370
pub fn extract_comment ( cursor : Cursor , returns : & [ Identifier ] ) -> Result < Option < NatSpec > > {
365
- // TODO: should we parse the doc comments inside of structs? filtering them out would add complexity
366
371
let mut cursor = cursor. spawn ( ) ;
367
372
let mut items = Vec :: new ( ) ;
368
- while cursor. go_to_next_terminal_with_kinds ( & [
369
- TerminalKind :: MultiLineNatSpecComment ,
370
- TerminalKind :: SingleLineNatSpecComment ,
371
- ] ) {
372
- let comment = & cursor. node ( ) . unparse ( ) ;
373
- items. push (
374
- parse_comment
375
- . parse ( comment)
376
- . map_err ( |e| Error :: NatspecParsingError {
377
- parent : extract_parent_name ( cursor. clone ( ) ) ,
378
- span : cursor. text_range ( ) ,
379
- message : e. to_string ( ) ,
380
- } ) ?
381
- . populate_returns ( returns) ,
382
- ) ;
373
+ while cursor. go_to_next ( ) {
374
+ if cursor. node ( ) . is_terminal_with_kinds ( & [
375
+ TerminalKind :: MultiLineNatSpecComment ,
376
+ TerminalKind :: SingleLineNatSpecComment ,
377
+ ] ) {
378
+ let comment = & cursor. node ( ) . unparse ( ) ;
379
+ items. push ( (
380
+ cursor. node ( ) . kind ( ) . to_string ( ) , // the node type to differentiate multiline for single line
381
+ cursor. text_range ( ) . start . line , // the line number to remove unwanted single-line comments
382
+ parse_comment
383
+ . parse ( comment)
384
+ . map_err ( |e| Error :: NatspecParsingError {
385
+ parent : extract_parent_name ( cursor. clone ( ) ) ,
386
+ span : cursor. text_range ( ) ,
387
+ message : e. to_string ( ) ,
388
+ } ) ?
389
+ . populate_returns ( returns) ,
390
+ ) ) ;
391
+ } else if cursor. node ( ) . is_terminal_with_kinds ( & [
392
+ TerminalKind :: ConstructorKeyword ,
393
+ TerminalKind :: EnumKeyword ,
394
+ TerminalKind :: ErrorKeyword ,
395
+ TerminalKind :: EventKeyword ,
396
+ TerminalKind :: FunctionKeyword ,
397
+ TerminalKind :: ModifierKeyword ,
398
+ TerminalKind :: StructKeyword ,
399
+ ] ) | cursor
400
+ . node ( )
401
+ . is_nonterminal_with_kind ( NonterminalKind :: StateVariableAttributes )
402
+ {
403
+ // anything after this node should be ignored, because we enter the item's body
404
+ break ;
405
+ }
383
406
}
384
- let items = items. into_iter ( ) . reduce ( |mut acc, mut i| {
385
- acc. append ( & mut i) ;
386
- acc
387
- } ) ;
388
- Ok ( items)
407
+ if let Some ( "MultiLineNatSpecComment" ) = items. last ( ) . map ( |( kind, _, _) | kind. as_str ( ) ) {
408
+ // if the last comment is multiline, we ignore all previous comments
409
+ let ( _, _, natspec) = items. pop ( ) . expect ( "there should be at least one elem" ) ;
410
+ return Ok ( Some ( natspec) ) ;
411
+ }
412
+ // the last comment is single-line
413
+ // we need to take the comments (in reverse) up to an empty line or a multiline comment (exclusive)
414
+ let mut res = Vec :: new ( ) ;
415
+ let mut iter = items. into_iter ( ) . rev ( ) . peekable ( ) ;
416
+ while let Some ( ( _, item_line, item) ) = iter. next ( ) {
417
+ res. push ( item) ;
418
+ if let Some ( ( next_kind, next_line, _) ) = iter. peek ( ) {
419
+ if next_kind == "MultiLineNatSpecComment" || * next_line < item_line - 1 {
420
+ // the next comments up should be ignored
421
+ break ;
422
+ }
423
+ }
424
+ }
425
+ if res. is_empty ( ) {
426
+ return Ok ( None ) ;
427
+ }
428
+ Ok ( Some ( res. into_iter ( ) . rev ( ) . fold (
429
+ NatSpec :: default ( ) ,
430
+ |mut acc, mut i| {
431
+ acc. append ( & mut i) ;
432
+ acc
433
+ } ,
434
+ ) ) )
389
435
}
390
436
391
437
/// Extract identifiers from a CST node, filtered by label equal to `name`
@@ -917,19 +963,7 @@ mod tests {
917
963
Some ( Parent :: Contract ( "ParserTestFunny" . to_string ( ) ) ) ,
918
964
& items,
919
965
) ;
920
- assert_eq ! (
921
- item. natspec. as_ref( ) . unwrap( ) . items,
922
- vec![
923
- NatSpecItem {
924
- kind: NatSpecKind :: Notice ,
925
- comment: "The first variable" . to_string( )
926
- } ,
927
- NatSpecItem {
928
- kind: NatSpecKind :: Notice ,
929
- comment: "The first variable" . to_string( )
930
- }
931
- ]
932
- ) ;
966
+ assert_eq ! ( item. natspec, None ) ;
933
967
}
934
968
935
969
#[ test]
@@ -982,14 +1016,44 @@ mod tests {
982
1016
assert_eq ! ( item. natspec, None ) ;
983
1017
}
984
1018
985
- #[ ignore]
986
1019
#[ test]
987
1020
fn test_parse_funny_function_private ( ) {
988
- // FIXME: should it parse the first comment or nah?
989
1021
let cursor = parse_file ( include_str ! ( "../../test-data/ParserTest.sol" ) ) ;
990
1022
let items = find_items ( cursor) ;
991
1023
let item = find_function (
992
- "_viewPrivate" ,
1024
+ "_viewPrivateMulti" ,
1025
+ Some ( Parent :: Contract ( "ParserTestFunny" . to_string ( ) ) ) ,
1026
+ & items,
1027
+ ) ;
1028
+ assert_eq ! (
1029
+ item. natspec. as_ref( ) . unwrap( ) . items,
1030
+ vec![
1031
+ NatSpecItem {
1032
+ kind: NatSpecKind :: Notice ,
1033
+ comment: "Some private stuff" . to_string( )
1034
+ } ,
1035
+ NatSpecItem {
1036
+ kind: NatSpecKind :: Param {
1037
+ name: "_paramName" . to_string( )
1038
+ } ,
1039
+ comment: "The parameter name" . to_string( )
1040
+ } ,
1041
+ NatSpecItem {
1042
+ kind: NatSpecKind :: Return {
1043
+ name: Some ( "_returned" . to_string( ) )
1044
+ } ,
1045
+ comment: "The returned value" . to_string( )
1046
+ } ,
1047
+ ]
1048
+ ) ;
1049
+ }
1050
+
1051
+ #[ test]
1052
+ fn test_parse_funny_function_private_single ( ) {
1053
+ let cursor = parse_file ( include_str ! ( "../../test-data/ParserTest.sol" ) ) ;
1054
+ let items = find_items ( cursor) ;
1055
+ let item = find_function (
1056
+ "_viewPrivateSingle" ,
993
1057
Some ( Parent :: Contract ( "ParserTestFunny" . to_string ( ) ) ) ,
994
1058
& items,
995
1059
) ;
0 commit comments