Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add test for unnamed returns #22

Merged
merged 8 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions sample-data/BasicSample.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ contract BasicSample is AbstractBasic {
return (true, 111);
}

/**
* @notice External function that returns a bool
* @dev A dev comment
* @return Some return data
*/
function externalSimpleMultipleUnnamedReturn() external pure returns (bool, uint256) {
return (true, 111);
}

/**
* @notice This function should have an inheritdoc tag
*/
Expand Down
7 changes: 7 additions & 0 deletions sample-data/ParserTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ contract ParserTest is IParserTest {
/// @notice Separate line
function _viewDuplicateTag() internal pure {
}

/// fun fact: there are extra spaces after the 1st return
/// @return
/// @return
function functionUnnamedEmptyReturn() external view returns (uint256, bool){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move this function to ParserTestFunny? It's the contract that acts as a collection of weird natspec.

return (1, true);
}
}

// This is a contract with invalid / missing natspec
Expand Down
2 changes: 1 addition & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function parseNodeNatspec(node: NodeToProcess): Natspec {
result.inheritdoc = { content: tagMatch[2] };
}
} else if (tagName === 'param' || tagName === 'return') {
const tagMatch = line.match(/^\s*@(\w+) *(\w+) (.*)$/);
const tagMatch = line.match(/^\s*@(\w+) *(\w*) *(.*)$/);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job catching this 👍🏻

if (tagMatch) {
currentTag = { name: tagMatch[2], content: tagMatch[3].trim() };
result[tagName === 'param' ? 'params' : 'returns'].push(currentTag);
Expand Down
16 changes: 6 additions & 10 deletions src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,13 @@ export class Validator {
let functionReturns = node.vReturnParameters.vParameters.map((p) => p.name);

// Make sure all defined returns have natspec
for (let paramName of functionReturns) {
if (!natspecReturns.includes(paramName)) {
let message = paramName === '' ? '@return missing for unnamed return' : `@return ${paramName} is missing`;
for (let [paramIndex, paramName] of functionReturns.entries()) {
if (paramIndex > natspecReturns.length - 1) {
let message = paramName === '' ? `@return missing for unnamed return №${paramIndex + 1}` : `@return ${paramName} is missing`;
alerts.push(message);
} else if (natspecReturns[paramIndex] !== paramName && paramName !== '') {
let message = `@return ${paramName} is missing`;
alerts.push(message);
}
}

// Make sure there is no natspec defined for non-existing returns
for (let paramName of natspecReturns) {
if (paramName && !functionReturns.includes(paramName)) {
alerts.push(`Missing named return for: @return ${paramName}`);
}
}

Expand Down
22 changes: 22 additions & 0 deletions test/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,28 @@ describe('Parser', () => {
})
);
});

it('should correctly parse empty return tag', async () => {
const node = contract.vFunctions.find(({ name }) => name === 'functionUnnamedEmptyReturn')!;
const result = parseNodeNatspec(node);

expect(result).toEqual(
mockNatspec({
tags: [],
params: [],
returns: [
{
name: '',
content: '',
},
{
name: '',
content: '',
},
],
})
);
});
});

describe('Interface', () => {
Expand Down
167 changes: 166 additions & 1 deletion test/validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,172 @@ describe('Validator', () => {
};

const result = validator.validate(node, natspec);
expect(result).toContainEqual(`@return missing for unnamed return`);
expect(result).toContainEqual(`@return missing for unnamed return №2`);
});

it('should warn of missed unnamed return', () => {
dristpunk marked this conversation as resolved.
Show resolved Hide resolved
node = contract.vFunctions.find(({ name }) => name === 'externalSimpleMultipleUnnamedReturn')!;
let natspec = {
tags: [
{
name: 'notice',
content: 'External function that returns a bool',
},
{
name: 'dev',
content: 'A dev comment',
},
],
params: [],
returns: [
{
name: 'Some',
content: 'return data',
},
],
};

const result = validator.validate(node, natspec);
expect(result).toEqual([`@return missing for unnamed return №2`]); // only 1 warning
});

it('should not warn of extra natspec tags', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code for this case won't compile, right? If so we can delete the test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code for this case won't compile, right? If so we can delete the test.

right, and a couple of more tests

node = contract.vFunctions.find(({ name }) => name === 'externalSimpleMultipleUnnamedReturn')!;
let natspec = {
tags: [
{
name: 'notice',
content: 'External function that returns a bool',
},
{
name: 'dev',
content: 'A dev comment',
},
],
params: [],
returns: [
{
name: 'Some',
content: 'return data',
},
{
name: 'Some',
content: 'return data',
},
],
};

const result = validator.validate(node, natspec);
expect(result).toEqual([]); // no warnings
});

it('should warn if tags are in a wrong order', () => {
node = contract.vFunctions.find(({ name }) => name === 'externalSimpleMultipleReturn')!;
let natspec = {
tags: [
{
name: 'notice',
content: 'External function that returns a bool',
},
{
name: 'dev',
content: 'A dev comment',
},
],
params: [
{
name: '_magicNumber',
content: 'A parameter description',
},
{
name: '_name',
content: 'Another parameter description',
},
],
returns: [
{
name: 'Some',
content: 'return data',
},
{
name: '_isMagic',
content: 'Some return data',
},
],
};

const result = validator.validate(node, natspec);
expect(result).toEqual(['@return _isMagic is missing']); // 1 warning
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning is misleading here. As a user, I will check that the @return _isMagic is there and think that the tool has mixed something up. Some will realize it's because of the order but many won't. We should either make it the message or avoid printing any warnings in case of incorrect ordering.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning is misleading here. As a user, I will check that the @return _isMagic is there and think that the tool has mixed something up. Some will realize it's because of the order but many won't. We should either make it the message or avoid printing any warnings in case of incorrect ordering.

this won't compile as well so I'll delete it

});

it('should warn all returns if the first natspec tag is missing', () => {
node = contract.vFunctions.find(({ name }) => name === 'externalSimpleMultipleReturn')!;
let natspec = {
tags: [
{
name: 'notice',
content: 'External function that returns a bool',
},
{
name: 'dev',
content: 'A dev comment',
},
],
params: [
{
name: '_magicNumber',
content: 'A parameter description',
},
{
name: '_name',
content: 'Another parameter description',
},
],
returns: [
{
name: 'Some',
content: 'return data',
},
],
};

const result = validator.validate(node, natspec);
expect(result).toEqual(['@return _isMagic is missing', '@return missing for unnamed return №2']); // 2 warnings
});

it('should warn if the last natspec tag is missing', () => {
node = contract.vFunctions.find(({ name }) => name === 'externalSimpleMultipleReturn')!;
let natspec = {
tags: [
{
name: 'notice',
content: 'External function that returns a bool',
},
{
name: 'dev',
content: 'A dev comment',
},
],
params: [
{
name: '_magicNumber',
content: 'A parameter description',
},
{
name: '_name',
content: 'Another parameter description',
},
],
returns: [
{
name: '_isMagic',
content: 'Some return data',
},
],
};

const result = validator.validate(node, natspec);
expect(result).toEqual(['@return missing for unnamed return №2']); // 1 warnings
});

// TODO: Check overridden functions, virtual, etc?
Expand Down
Loading