import * as _ from 'lodash'; import * as chai from 'chai'; import 'mocha'; import { DocAgnosticFormat, Event, SolidityMethod } from '@0xproject/types'; import { generateSolDocAsync } from '../src/solidity_doc_generator'; import { chaiSetup } from './util/chai_setup'; chaiSetup.configure(); const expect = chai.expect; describe('#SolidityDocGenerator', () => { it('should generate a doc object that matches the devdoc-free fixture', async () => { const doc = await generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [ 'TokenTransferProxyNoDevdoc', ]); expect(doc).to.not.be.undefined(); verifyTokenTransferProxyABIIsDocumented(doc, 'TokenTransferProxyNoDevdoc'); }); const docPromises: Array> = [ generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`), generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, []), generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, ['TokenTransferProxy']), ]; docPromises.forEach(docPromise => { it('should generate a doc object that matches the fixture', async () => { const doc = await docPromise; expect(doc).to.not.be.undefined(); verifyTokenTransferProxyABIIsDocumented(doc, 'TokenTransferProxy'); let addAuthorizedAddressMethod: SolidityMethod | undefined; for (const method of doc.TokenTransferProxy.methods) { if (method.name === 'addAuthorizedAddress') { addAuthorizedAddressMethod = method; } } const tokenTransferProxyAddAuthorizedAddressComment = 'Authorizes an address.'; expect((addAuthorizedAddressMethod as SolidityMethod).comment).to.equal( tokenTransferProxyAddAuthorizedAddressComment, ); const expectedParamComment = 'Address to authorize.'; expect((addAuthorizedAddressMethod as SolidityMethod).parameters[0].comment).to.equal(expectedParamComment); }); }); describe('when processing all the permutations of devdoc stuff that we use in our contracts', () => { let doc: DocAgnosticFormat; before(async () => { doc = await generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, ['NatspecEverything']); expect(doc).to.not.be.undefined(); expect(doc.NatspecEverything).to.not.be.undefined(); }); it('should emit the contract @title as its comment', () => { expect(doc.NatspecEverything.comment).to.equal('Contract Title'); }); describe('should emit public method documentation for', () => { let methodDoc: SolidityMethod; before(() => { // tslint:disable-next-line:no-unnecessary-type-assertion methodDoc = doc.NatspecEverything.methods.find(method => { return method.name === 'publicMethod'; }) as SolidityMethod; if (_.isUndefined(methodDoc)) { throw new Error('publicMethod not found'); } }); it('method name', () => { expect(methodDoc.name).to.equal('publicMethod'); }); it('method comment', () => { expect(methodDoc.comment).to.equal('publicMethod @dev'); }); it('parameter name', () => { expect(methodDoc.parameters[0].name).to.equal('p'); }); it('parameter comment', () => { expect(methodDoc.parameters[0].comment).to.equal('publicMethod @param'); }); it('return type name', () => { expect(methodDoc.returnType.name).to.equal('r'); }); }); describe('should emit external method documentation for', () => { let methodDoc: SolidityMethod; before(() => { // tslint:disable-next-line:no-unnecessary-type-assertion methodDoc = doc.NatspecEverything.methods.find(method => { return method.name === 'externalMethod'; }) as SolidityMethod; if (_.isUndefined(methodDoc)) { throw new Error('externalMethod not found'); } }); it('method name', () => { expect(methodDoc.name).to.equal('externalMethod'); }); it('method comment', () => { expect(methodDoc.comment).to.equal('externalMethod @dev'); }); it('parameter name', () => { expect(methodDoc.parameters[0].name).to.equal('p'); }); it('parameter comment', () => { expect(methodDoc.parameters[0].comment).to.equal('externalMethod @param'); }); it('return type name', () => { expect(methodDoc.returnType.name).to.equal('r'); }); }); it('should not truncate a multi-line devdoc comment', () => { // tslint:disable-next-line:no-unnecessary-type-assertion const methodDoc: SolidityMethod = doc.NatspecEverything.methods.find(method => { return method.name === 'methodWithLongDevdoc'; }) as SolidityMethod; if (_.isUndefined(methodDoc)) { throw new Error('methodWithLongDevdoc not found'); } expect(methodDoc.comment).to.equal( 'Here is a really long developer documentation comment, which spans multiple lines, for the purposes of making sure that broken lines are consolidated into one devdoc comment.', ); }); describe('should emit event documentation for', () => { let eventDoc: Event; before(() => { eventDoc = (doc.NatspecEverything.events as Event[])[0]; }); it('event name', () => { expect(eventDoc.name).to.equal('AnEvent'); }); it('parameter name', () => { expect(eventDoc.eventArgs[0].name).to.equal('p'); }); }); it('should not let solhint directives obscure natspec content', () => { // tslint:disable-next-line:no-unnecessary-type-assertion const methodDoc: SolidityMethod = doc.NatspecEverything.methods.find(method => { return method.name === 'methodWithSolhintDirective'; }) as SolidityMethod; if (_.isUndefined(methodDoc)) { throw new Error('methodWithSolhintDirective not found'); } expect(methodDoc.comment).to.equal('methodWithSolhintDirective @dev'); }); }); }); function verifyTokenTransferProxyABIIsDocumented(doc: DocAgnosticFormat, contractName: string): void { expect(doc[contractName]).to.not.be.undefined(); expect(doc[contractName].constructors).to.not.be.undefined(); const tokenTransferProxyConstructorCount = 0; const tokenTransferProxyMethodCount = 8; const tokenTransferProxyEventCount = 3; expect(doc[contractName].constructors.length).to.equal(tokenTransferProxyConstructorCount); expect(doc[contractName].methods.length).to.equal(tokenTransferProxyMethodCount); const events = doc[contractName].events; if (_.isUndefined(events)) { throw new Error('events should never be undefined'); } expect(events.length).to.equal(tokenTransferProxyEventCount); expect(doc.Ownable).to.not.be.undefined(); expect(doc.Ownable.constructors).to.not.be.undefined(); expect(doc.Ownable.methods).to.not.be.undefined(); const ownableConstructorCount = 1; const ownableMethodCount = 2; const ownableEventCount = 1; expect(doc.Ownable.constructors.length).to.equal(ownableConstructorCount); expect(doc.Ownable.methods.length).to.equal(ownableMethodCount); if (_.isUndefined(doc.Ownable.events)) { throw new Error('events should never be undefined'); } expect(doc.Ownable.events.length).to.equal(ownableEventCount); expect(doc.ERC20).to.not.be.undefined(); expect(doc.ERC20.constructors).to.not.be.undefined(); expect(doc.ERC20.methods).to.not.be.undefined(); const erc20ConstructorCount = 0; const erc20MethodCount = 6; const erc20EventCount = 2; expect(doc.ERC20.constructors.length).to.equal(erc20ConstructorCount); expect(doc.ERC20.methods.length).to.equal(erc20MethodCount); if (_.isUndefined(doc.ERC20.events)) { throw new Error('events should never be undefined'); } expect(doc.ERC20.events.length).to.equal(erc20EventCount); expect(doc.ERC20Basic).to.not.be.undefined(); expect(doc.ERC20Basic.constructors).to.not.be.undefined(); expect(doc.ERC20Basic.methods).to.not.be.undefined(); const erc20BasicConstructorCount = 0; const erc20BasicMethodCount = 3; const erc20BasicEventCount = 1; expect(doc.ERC20Basic.constructors.length).to.equal(erc20BasicConstructorCount); expect(doc.ERC20Basic.methods.length).to.equal(erc20BasicMethodCount); if (_.isUndefined(doc.ERC20Basic.events)) { throw new Error('events should never be undefined'); } expect(doc.ERC20Basic.events.length).to.equal(erc20BasicEventCount); let addAuthorizedAddressMethod: SolidityMethod | undefined; for (const method of doc[contractName].methods) { if (method.name === 'addAuthorizedAddress') { addAuthorizedAddressMethod = method; } } expect( addAuthorizedAddressMethod, `method addAuthorizedAddress not found in ${JSON.stringify(doc[contractName].methods)}`, ).to.not.be.undefined(); }