Skip to content

Commit dc25919

Browse files
authored
fix(definitions): comments filtering (#35)
* docs: comment * fix(definitions): comments extraction
1 parent 27d31fb commit dc25919

File tree

2 files changed

+116
-40
lines changed

2 files changed

+116
-40
lines changed

src/definitions/mod.rs

+101-37
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,13 @@ pub enum Definition {
165165
}
166166

167167
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.
168172
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.
169175
match (self, other) {
170176
(Self::Constructor(a), Self::Constructor(b)) => a.span.start == b.span.start,
171177
(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>
362368

363369
/// Extract and parse the NatSpec comment information, if any
364370
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
366371
let mut cursor = cursor.spawn();
367372
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+
}
383406
}
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+
)))
389435
}
390436

391437
/// Extract identifiers from a CST node, filtered by label equal to `name`
@@ -917,19 +963,7 @@ mod tests {
917963
Some(Parent::Contract("ParserTestFunny".to_string())),
918964
&items,
919965
);
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);
933967
}
934968

935969
#[test]
@@ -982,14 +1016,44 @@ mod tests {
9821016
assert_eq!(item.natspec, None);
9831017
}
9841018

985-
#[ignore]
9861019
#[test]
9871020
fn test_parse_funny_function_private() {
988-
// FIXME: should it parse the first comment or nah?
9891021
let cursor = parse_file(include_str!("../../test-data/ParserTest.sol"));
9901022
let items = find_items(cursor);
9911023
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",
9931057
Some(Parent::Contract("ParserTestFunny".to_string())),
9941058
&items,
9951059
);

test-data/ParserTest.sol

+15-3
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,26 @@ contract ParserTestFunny is IParserTest {
156156
*
157157
*
158158
*
159-
* I met Obama once
160-
* She's cool
159+
* This should be ignored
161160
*/
162161

162+
/**
163+
* @notice Some private stuff
164+
* @param _paramName The parameter name
165+
* @return _returned The returned value
166+
*/
167+
function _viewPrivateMulti(uint256 _paramName) private pure returns (uint256 _returned) {
168+
return 1;
169+
}
170+
171+
/// @dev This should be ignored
172+
/**
173+
* @dev this too
174+
*/
163175
/// @notice Some private stuff
164176
/// @param _paramName The parameter name
165177
/// @return _returned The returned value
166-
function _viewPrivate(uint256 _paramName) private pure returns (uint256 _returned) {
178+
function _viewPrivateSingle(uint256 _paramName) private pure returns (uint256 _returned) {
167179
return 1;
168180
}
169181

0 commit comments

Comments
 (0)