Skip to content

Commit f1c0d5f

Browse files
authored
fix: natspec parser and function returns validation (#37)
* test: add insta and similar-asserts * test: add snapshot tests * docs: typo * chore: update deps * test: rename snapshot test * test: rename test functions * fix: natspec parser and function returns validation
1 parent dc25919 commit f1c0d5f

33 files changed

+813
-11
lines changed

Cargo.lock

+88-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+8
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,18 @@ slang_solidity = "0.18.3"
3131
thiserror = "2.0.11"
3232
winnow = "0.7.2"
3333

34+
[dev-dependencies]
35+
insta = "1.42.1"
36+
similar-asserts = "1.6.1"
37+
3438
[profile.release]
3539
lto = "thin"
3640

3741
# The profile that 'dist' will build with
3842
[profile.dist]
3943
inherits = "release"
4044
lto = "thin"
45+
46+
[profile.dev.package]
47+
insta.opt-level = 3
48+
similar.opt-level = 3

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ which take precedence over the config file.
8888

8989
## Usage in GitHub Actions
9090

91-
You can check your code in CI with the lintspec GitHub Action. Any `.lintspec.toml` or `.nsignore` file in
91+
You can check your code in CI with the lintspec GitHub Action. Any `.lintspec.toml` or `.nsignore` file in the
9292
repository's root will be used to configure the execution.
9393

9494
The action generates

flake.nix

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
buildInputs = [
2525
pkgs.rust-analyzer-unwrapped
2626
toolchain
27+
pkgs.cargo-insta
28+
pkgs.cargo-nextest
2729
];
2830

2931
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";

src/definitions/constructor.rs

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ impl Validate for ConstructorDefinition {
9191
#[cfg(test)]
9292
mod tests {
9393
use semver::Version;
94+
use similar_asserts::assert_eq;
9495
use slang_solidity::parser::Parser;
9596

9697
use super::*;

src/definitions/enumeration.rs

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ fn extract_enum_members(cursor: Cursor) -> Vec<Identifier> {
106106
#[cfg(test)]
107107
mod tests {
108108
use semver::Version;
109+
use similar_asserts::assert_eq;
109110
use slang_solidity::{cst::NonterminalKind, parser::Parser};
110111

111112
use super::*;

src/definitions/error.rs

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ impl Validate for ErrorDefinition {
9494
#[cfg(test)]
9595
mod tests {
9696
use semver::Version;
97+
use similar_asserts::assert_eq;
9798
use slang_solidity::{cst::NonterminalKind, parser::Parser};
9899

99100
use super::*;

src/definitions/event.rs

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ impl Validate for EventDefinition {
9494
#[cfg(test)]
9595
mod tests {
9696
use semver::Version;
97+
use similar_asserts::assert_eq;
9798
use slang_solidity::{cst::NonterminalKind, parser::Parser};
9899

99100
use super::*;

src/definitions/function.rs

+20-2
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,13 @@ impl Validate for FunctionDefinition {
119119
if self.name == "receive" || self.name == "fallback" {
120120
return out;
121121
}
122-
// raise error if no NatSpec is available (unless there are no params and we don't enforce inheritdoc)
122+
// raise error if no NatSpec is available (unless there are no params, no returns, and we don't enforce
123+
// inheritdoc)
123124
let Some(natspec) = &self.natspec else {
124-
if self.params.is_empty() && (!options.inheritdoc || !self.requires_inheritdoc()) {
125+
if self.returns.is_empty()
126+
&& self.params.is_empty()
127+
&& (!options.inheritdoc || !self.requires_inheritdoc())
128+
{
125129
return out;
126130
}
127131
// we require natspec for either inheritdoc or the params
@@ -156,6 +160,7 @@ impl Validate for FunctionDefinition {
156160
#[cfg(test)]
157161
mod tests {
158162
use semver::Version;
163+
use similar_asserts::assert_eq;
159164
use slang_solidity::parser::Parser;
160165

161166
use super::*;
@@ -362,4 +367,17 @@ mod tests {
362367
assert_eq!(res.diags.len(), 1);
363368
assert_eq!(res.diags[0].message, "@inheritdoc is missing");
364369
}
370+
371+
#[test]
372+
fn test_function_internal() {
373+
let contents = "contract Test {
374+
// @notice
375+
function _viewInternal() internal view returns (uint256) {
376+
return 1;
377+
}
378+
}";
379+
let res = parse_file(contents).validate(&OPTIONS);
380+
assert_eq!(res.diags.len(), 1);
381+
assert_eq!(res.diags[0].message, "missing NatSpec");
382+
}
365383
}

src/definitions/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ pub fn extract_parent_name(mut cursor: Cursor) -> Option<Parent> {
576576

577577
#[cfg(test)]
578578
mod tests {
579+
use similar_asserts::assert_eq;
579580
use slang_solidity::parser::Parser;
580581

581582
use crate::{

src/definitions/modifier.rs

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ impl Validate for ModifierDefinition {
133133
#[cfg(test)]
134134
mod tests {
135135
use semver::Version;
136+
use similar_asserts::assert_eq;
136137
use slang_solidity::parser::Parser;
137138

138139
use super::*;

src/definitions/structure.rs

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ fn extract_struct_members(cursor: Cursor) -> Result<Vec<Identifier>> {
113113
#[cfg(test)]
114114
mod tests {
115115
use semver::Version;
116+
use similar_asserts::assert_eq;
116117
use slang_solidity::{cst::NonterminalKind, parser::Parser};
117118

118119
use super::*;

src/definitions/variable.rs

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ impl Validate for VariableDeclaration {
119119
#[cfg(test)]
120120
mod tests {
121121
use semver::Version;
122+
use similar_asserts::assert_eq;
122123
use slang_solidity::{cst::NonterminalKind, parser::Parser};
123124

124125
use super::*;

src/natspec.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,11 @@ fn parse_comment_line_single(input: &mut &str) -> Result<NatSpecItem> {
227227
}
228228

229229
fn parse_single_line_comment(input: &mut &str) -> Result<NatSpec> {
230-
let item = preceded("///", parse_comment_line_single).parse_next(input)?;
230+
let item = preceded(
231+
repeat::<_, _, (), _, _>(3.., '/'),
232+
parse_comment_line_single,
233+
)
234+
.parse_next(input)?;
231235
if item.is_empty() {
232236
return Ok(NatSpec::default());
233237
}
@@ -236,6 +240,8 @@ fn parse_single_line_comment(input: &mut &str) -> Result<NatSpec> {
236240

237241
#[cfg(test)]
238242
mod tests {
243+
use similar_asserts::assert_eq;
244+
239245
use super::*;
240246

241247
#[test]
@@ -434,4 +440,24 @@ mod tests {
434440
let res = res.unwrap();
435441
assert_eq!(res, NatSpec::default());
436442
}
443+
444+
#[ignore]
445+
#[test]
446+
fn test_multiline_weird() {
447+
// FIXME: this doesn't pass
448+
let comment = "/**** @notice Some text
449+
** */";
450+
let res = parse_comment.parse(comment);
451+
assert!(res.is_ok(), "{res:?}");
452+
let res = res.unwrap();
453+
assert_eq!(
454+
res,
455+
NatSpec {
456+
items: vec![NatSpecItem {
457+
kind: NatSpecKind::Notice,
458+
comment: "Some text".to_string()
459+
},]
460+
}
461+
);
462+
}
437463
}

test-data/BasicSample.sol

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ contract BasicSample is AbstractBasic {
1414
uint256 someNumber;
1515
}
1616

17+
/// @notice An enum
18+
enum TestEnum {
19+
First,
20+
Second
21+
}
22+
1723
/**
1824
* @notice Some error missing parameter natspec
1925
*/

test-data/InterfaceSample.sol

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity =0.8.19;
3+
4+
interface IInterfacedSample {
5+
/**
6+
* @notice Greets the caller
7+
*
8+
* @return _greeting The greeting
9+
* @return _balance Current token balance of the caller
10+
*/
11+
function greet() external view returns (string memory _greeting, uint256 _balance);
12+
}
13+
14+
contract InterfacedSample is IInterfacedSample {
15+
/// @inheritdoc IInterfacedSample
16+
/// @dev some dev thingy
17+
function greet() external view returns (string memory _greeting, uint256 _balance) {}
18+
}

0 commit comments

Comments
 (0)