Skip to content

Commit

Permalink
feat: unnamed returns (#22)
Browse files Browse the repository at this point in the history
Co-authored-by: Gas <[email protected]>
  • Loading branch information
dristpunk and gas1cent authored Jan 26, 2024
1 parent d5c64f7 commit cf9fe52
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 12 deletions.
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 @@ -179,5 +179,12 @@ contract ParserTestFunny is IParserTest {
function _viewLinterFail() internal pure {

}

/// fun fact: there are extra spaces after the 1st return
/// @return
/// @return
function functionUnnamedEmptyReturn() external view returns (uint256, bool){
return (1, true);
}
}
// forgefmt: disable-end
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*) *(.*)$/);
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 @@ -420,5 +420,27 @@ 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: '',
},
],
})
);
});
});
});
98 changes: 97 additions & 1 deletion test/validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,103 @@ 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 a missing unnamed return', () => {
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 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

0 comments on commit cf9fe52

Please sign in to comment.