Skip to content
This repository was archived by the owner on Sep 27, 2020. It is now read-only.

Commit 9581f68

Browse files
vibern0federicobond
authored andcommitted
Add support for NatSpec comments
1 parent 8f63dcb commit 9581f68

File tree

4 files changed

+198
-1
lines changed

4 files changed

+198
-1
lines changed

.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
},
1616
"env": {
1717
"es6": true,
18+
"mocha": true,
1819
"node": true
1920
}
2021
}

src/ASTBuilder.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const antlr4 = require('./antlr4/index')
2+
const parseComments = require('./natspec')
23

34
function toText(ctx) {
45
if (ctx !== null) {
@@ -116,13 +117,22 @@ const transformAST = {
116117

117118
ContractDefinition(ctx) {
118119
const name = toText(ctx.identifier())
120+
let natspec = null
121+
let kind
122+
if (ctx.natSpec()) {
123+
natspec = parseComments(toText(ctx.getChild(0)))
124+
kind = toText(ctx.getChild(1))
125+
} else {
126+
kind = toText(ctx.getChild(0))
127+
}
119128
this._currentContract = name
120129

121130
return {
131+
natspec,
122132
name,
123133
baseContracts: this.visit(ctx.inheritanceSpecifier()),
124134
subNodes: this.visit(ctx.contractPart()),
125-
kind: toText(ctx.getChild(0))
135+
kind
126136
}
127137
},
128138

@@ -215,7 +225,13 @@ const transformAST = {
215225
stateMutability = toText(ctx.modifierList().stateMutability(0))
216226
}
217227

228+
let natspec = null
229+
if (ctx.natSpec()) {
230+
natspec = parseComments(toText(ctx.getChild(0)))
231+
}
232+
218233
return {
234+
natspec,
219235
name,
220236
parameters,
221237
returnParameters,
@@ -902,7 +918,12 @@ const transformAST = {
902918
},
903919

904920
EventDefinition(ctx) {
921+
let natspec = null
922+
if (ctx.natSpec()) {
923+
natspec = parseComments(toText(ctx.getChild(0)))
924+
}
905925
return {
926+
natspec,
906927
name: toText(ctx.identifier()),
907928
parameters: this.visit(ctx.eventParameterList()),
908929
isAnonymous: !!ctx.AnonymousKeyword()

src/natspec.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Parse a full text comment to an object that can be easly consumed
3+
* @param comment full text comment
4+
*/
5+
module.exports = function(comment) {
6+
// remove comments definers
7+
// the comments can be multiline of single line
8+
if (comment.substring(0, 3) === '/**') {
9+
// if it is a multiline, remove line breaks, "/**"" (the comment begin)
10+
// "*/"" (the comment end), and "* @" at the begining of each comment
11+
comment = comment.replace(/(\r|\n|\/\*\*|\*\/|\*(?=[ ]?@))/g, '')
12+
} else if (comment.substring(0, 3) === '///') {
13+
// if it is a single line,
14+
// remove just line breakers and "///" (the comment begin)
15+
comment = comment.replace(/(\r|\n|\/\/\/)/g, '')
16+
}
17+
18+
// split text by comment type
19+
const splitComments = comment.split(
20+
/@(title|author|notice|dev|param|return)/g
21+
)
22+
// let's start a map
23+
const resultComments = {}
24+
25+
// iterate through all the split comments
26+
// start in the second result (because the first one is usually nothing)
27+
// then iterate each two elements, because since the split is done using
28+
// the natspec type
29+
for (let x = 1; x < splitComments.length; x += 2) {
30+
const key = splitComments[x]
31+
const value = splitComments[x + 1].trim()
32+
33+
// if the comment if type 'param' we need to extract the first word
34+
// which is the variable name
35+
if (key === 'param') {
36+
const sep = value.indexOf(' ')
37+
const paramName = value.substring(0, sep)
38+
const paramValue = value.substring(sep + 1)
39+
40+
if (resultComments['params'] == null) {
41+
resultComments['params'] = {}
42+
}
43+
44+
resultComments['params'][paramName] = paramValue
45+
} else {
46+
// finally, if the value was found, update it
47+
resultComments[key] = value
48+
}
49+
}
50+
return resultComments
51+
}

test/ast.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ describe('AST', () => {
8484
var ast = parseContract("contract test {}")
8585
assert.deepEqual(ast, {
8686
"type": "ContractDefinition",
87+
"natspec": null,
8788
"name": "test",
8889
"baseContracts": [],
8990
"subNodes": [],
@@ -94,6 +95,7 @@ describe('AST', () => {
9495
ast = parseContract("contract test is foo, bar {}")
9596
assert.deepEqual(ast, {
9697
"type": "ContractDefinition",
98+
"natspec": null,
9799
"name": "test",
98100
"baseContracts": [
99101
{
@@ -121,6 +123,7 @@ describe('AST', () => {
121123
ast = parseContract("library test {}")
122124
assert.deepEqual(ast, {
123125
"type": "ContractDefinition",
126+
"natspec": null,
124127
"name": "test",
125128
"baseContracts": [],
126129
"subNodes": [],
@@ -131,6 +134,7 @@ describe('AST', () => {
131134
ast = parseContract("interface test {}")
132135
assert.deepEqual(ast, {
133136
"type": "ContractDefinition",
137+
"natspec": null,
134138
"name": "test",
135139
"baseContracts": [],
136140
"subNodes": [],
@@ -171,6 +175,7 @@ describe('AST', () => {
171175
var ast = parseNode("function foo(uint a) pure {}")
172176
assert.deepEqual(ast, {
173177
"type": "FunctionDefinition",
178+
"natspec": null,
174179
"name": "foo",
175180
"parameters": [
176181
{
@@ -200,6 +205,7 @@ describe('AST', () => {
200205
ast = parseNode("function foo(uint a) pure returns (uint256) {}")
201206
assert.deepEqual(ast, {
202207
"type": "FunctionDefinition",
208+
"natspec": null,
203209
"name": "foo",
204210
"parameters": [
205211
{
@@ -1091,6 +1097,7 @@ describe('AST', () => {
10911097
assert.deepEqual(ast, {
10921098
"type": "EventDefinition",
10931099
"name": "Foo",
1100+
"natspec": null,
10941101
"parameters": [
10951102
{
10961103
"type": "VariableDeclaration",
@@ -1490,4 +1497,121 @@ describe('AST', () => {
14901497
"type": "AssemblyIf"
14911498
})
14921499
})
1500+
1501+
it("NatSpec multi line", function () {
1502+
const ast = parser.parse(
1503+
`/**
1504+
* @dev This is the Sum contract.
1505+
* @title Sum Contract
1506+
* @author username
1507+
*/
1508+
contract Sum { }`
1509+
);
1510+
assert.deepEqual(ast.children[0], {
1511+
type: "ContractDefinition",
1512+
natspec: {
1513+
dev: "This is the Sum contract.",
1514+
title: "Sum Contract",
1515+
author: "username",
1516+
},
1517+
name: "Sum",
1518+
baseContracts: [],
1519+
subNodes: [],
1520+
kind: "contract",
1521+
})
1522+
})
1523+
1524+
it("NatSpec single line", function () {
1525+
const ast = parser.parse(
1526+
`/// @dev This is the Sum contract.
1527+
/// @title Sum Contract
1528+
/// @author username
1529+
contract Sum { }`
1530+
);
1531+
assert.deepEqual(ast.children[0], {
1532+
type: "ContractDefinition",
1533+
natspec: {
1534+
dev: "This is the Sum contract.",
1535+
title: "Sum Contract",
1536+
author: "username",
1537+
},
1538+
name: "Sum",
1539+
baseContracts: [],
1540+
subNodes: [],
1541+
kind: "contract",
1542+
})
1543+
})
1544+
1545+
it("NatSpec multi line event", function () {
1546+
const ast = parseNode(
1547+
`/**
1548+
* @dev This method says hello
1549+
* @param user the user address
1550+
*/
1551+
event sayHello(address user);`
1552+
);
1553+
assert.deepEqual(ast.natspec, {
1554+
dev: "This method says hello",
1555+
params: {
1556+
user: "the user address"
1557+
},
1558+
})
1559+
})
1560+
1561+
it("NatSpec multi line function", function () {
1562+
const ast = parseNode(
1563+
`/**
1564+
* @dev This method transfer fund to other user
1565+
* @param from the address extract funds
1566+
* @param to the user address to give funds
1567+
* @param amount the amount to transfer
1568+
*/
1569+
function transfer(address from, address to, uint256 amount) public {}`);
1570+
1571+
assert.deepEqual(ast.natspec, {
1572+
dev: "This method transfer fund to other user",
1573+
params: {
1574+
from: "the address extract funds",
1575+
to: "the user address to give funds",
1576+
amount: "the amount to transfer",
1577+
},
1578+
})
1579+
})
1580+
it("NatSpec multi line multiple functions in contract", function () {
1581+
const ast = parser.parse(
1582+
`/**
1583+
* @dev The ERC20 contract
1584+
*/
1585+
contract ERC20 {
1586+
/**
1587+
* @dev This method transfer fund to other user
1588+
* @param from the address extract funds
1589+
* @param to the user address to give funds
1590+
* @param amount the amount to transfer
1591+
*/
1592+
function transfer(address from, address to, uint256 amount) public {}
1593+
/**
1594+
* @dev This method gets the approved amount
1595+
* @param user the user address to verify
1596+
* @return the approved amount
1597+
*/
1598+
function approved(address user) public view returns(uint256) {}
1599+
}`);
1600+
const methods = ast.children[0].subNodes;
1601+
assert.deepEqual(methods[0].natspec, {
1602+
dev: 'This method transfer fund to other user',
1603+
params: {
1604+
from: 'the address extract funds',
1605+
to: 'the user address to give funds',
1606+
amount: 'the amount to transfer',
1607+
},
1608+
});
1609+
assert.deepEqual(methods[1].natspec, {
1610+
dev: 'This method gets the approved amount',
1611+
params: {
1612+
user: 'the user address to verify',
1613+
},
1614+
return: 'the approved amount',
1615+
});
1616+
})
14931617
})

0 commit comments

Comments
 (0)