aboutsummaryrefslogtreecommitdiffstats
path: root/packages/sol-doc/test
diff options
context:
space:
mode:
Diffstat (limited to 'packages/sol-doc/test')
-rw-r--r--packages/sol-doc/test/fixtures/contracts/MultipleReturnValues.sol7
-rw-r--r--packages/sol-doc/test/fixtures/contracts/NatspecEverything.sol40
-rw-r--r--packages/sol-doc/test/fixtures/contracts/StructParamAndReturn.sol18
-rw-r--r--packages/sol-doc/test/fixtures/contracts/TokenTransferProxy.sol115
-rw-r--r--packages/sol-doc/test/fixtures/contracts/TokenTransferProxyNoDevdoc.sol100
-rw-r--r--packages/sol-doc/test/solidity_doc_generator_test.ts273
-rw-r--r--packages/sol-doc/test/util/chai_setup.ts13
7 files changed, 566 insertions, 0 deletions
diff --git a/packages/sol-doc/test/fixtures/contracts/MultipleReturnValues.sol b/packages/sol-doc/test/fixtures/contracts/MultipleReturnValues.sol
new file mode 100644
index 000000000..1e898622c
--- /dev/null
+++ b/packages/sol-doc/test/fixtures/contracts/MultipleReturnValues.sol
@@ -0,0 +1,7 @@
+pragma solidity ^0.4.24;
+
+contract MultipleReturnValues {
+ function methodWithMultipleReturnValues() public pure returns(int, int) {
+ return (0, 0);
+ }
+}
diff --git a/packages/sol-doc/test/fixtures/contracts/NatspecEverything.sol b/packages/sol-doc/test/fixtures/contracts/NatspecEverything.sol
new file mode 100644
index 000000000..c6ad3db81
--- /dev/null
+++ b/packages/sol-doc/test/fixtures/contracts/NatspecEverything.sol
@@ -0,0 +1,40 @@
+pragma solidity ^0.4.24;
+
+/// @title Contract Title
+/// @dev This is a very long documentation comment at the contract level.
+/// It actually spans multiple lines, too.
+contract NatspecEverything {
+ int d;
+
+ /// @dev Constructor @dev
+ /// @param p Constructor @param
+ constructor(int p) public { d = p; }
+
+ /// @notice publicMethod @notice
+ /// @dev publicMethod @dev
+ /// @param p publicMethod @param
+ /// @return publicMethod @return
+ function publicMethod(int p) public pure returns(int r) { return p; }
+
+ /// @dev Fallback @dev
+ function () public {}
+
+ /// @notice externalMethod @notice
+ /// @dev externalMethod @dev
+ /// @param p externalMethod @param
+ /// @return externalMethod @return
+ function externalMethod(int p) external pure returns(int r) { return p; }
+
+ /// @dev 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.
+ function methodWithLongDevdoc(int p) public pure returns(int) { return p; }
+
+ /// @dev AnEvent @dev
+ /// @param p on this event is an integer.
+ event AnEvent(int p);
+
+ /// @dev methodWithSolhintDirective @dev
+ // solhint-disable no-empty-blocks
+ function methodWithSolhintDirective() public pure {}
+}
diff --git a/packages/sol-doc/test/fixtures/contracts/StructParamAndReturn.sol b/packages/sol-doc/test/fixtures/contracts/StructParamAndReturn.sol
new file mode 100644
index 000000000..b9a7ccdbc
--- /dev/null
+++ b/packages/sol-doc/test/fixtures/contracts/StructParamAndReturn.sol
@@ -0,0 +1,18 @@
+pragma solidity 0.4.24;
+pragma experimental ABIEncoderV2;
+
+
+contract StructParamAndReturn {
+
+ struct Stuff {
+ address anAddress;
+ uint256 aNumber;
+ }
+
+ /// @dev DEV_COMMENT
+ /// @param stuff STUFF_COMMENT
+ /// @return RETURN_COMMENT
+ function methodWithStructParamAndReturn(Stuff stuff) public pure returns(Stuff) {
+ return stuff;
+ }
+}
diff --git a/packages/sol-doc/test/fixtures/contracts/TokenTransferProxy.sol b/packages/sol-doc/test/fixtures/contracts/TokenTransferProxy.sol
new file mode 100644
index 000000000..44570d459
--- /dev/null
+++ b/packages/sol-doc/test/fixtures/contracts/TokenTransferProxy.sol
@@ -0,0 +1,115 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.14;
+
+import { Ownable } from "zeppelin-solidity/contracts/ownership/Ownable.sol";
+import { ERC20 as Token } from "zeppelin-solidity/contracts/token/ERC20/ERC20.sol";
+
+/// @title TokenTransferProxy - Transfers tokens on behalf of contracts that have been approved via decentralized governance.
+/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
+contract TokenTransferProxy is Ownable {
+
+ /// @dev Only authorized addresses can invoke functions with this modifier.
+ modifier onlyAuthorized {
+ require(authorized[msg.sender]);
+ _;
+ }
+
+ modifier targetAuthorized(address target) {
+ require(authorized[target]);
+ _;
+ }
+
+ modifier targetNotAuthorized(address target) {
+ require(!authorized[target]);
+ _;
+ }
+
+ mapping (address => bool) public authorized;
+ address[] public authorities;
+
+ event LogAuthorizedAddressAdded(address indexed target, address indexed caller);
+ event LogAuthorizedAddressRemoved(address indexed target, address indexed caller);
+
+ /*
+ * Public functions
+ */
+
+ /// @dev Authorizes an address.
+ /// @param target Address to authorize.
+ function addAuthorizedAddress(address target)
+ public
+ onlyOwner
+ targetNotAuthorized(target)
+ {
+ authorized[target] = true;
+ authorities.push(target);
+ LogAuthorizedAddressAdded(target, msg.sender);
+ }
+
+ /// @dev Removes authorizion of an address.
+ /// @param target Address to remove authorization from.
+ function removeAuthorizedAddress(address target)
+ public
+ onlyOwner
+ targetAuthorized(target)
+ {
+ delete authorized[target];
+ for (uint i = 0; i < authorities.length; i++) {
+ if (authorities[i] == target) {
+ authorities[i] = authorities[authorities.length - 1];
+ authorities.length -= 1;
+ break;
+ }
+ }
+ LogAuthorizedAddressRemoved(target, msg.sender);
+ }
+
+ /// @dev Calls into ERC20 Token contract, invoking transferFrom.
+ /// @param token Address of token to transfer.
+ /// @param from Address to transfer token from.
+ /// @param to Address to transfer token to.
+ /// @param value Amount of token to transfer.
+ /// @return Success of transfer.
+ function transferFrom(
+ address token,
+ address from,
+ address to,
+ uint value)
+ public
+ onlyAuthorized
+ returns (bool)
+ {
+ return Token(token).transferFrom(from, to, value);
+ }
+
+ /*
+ * Public constant functions
+ */
+
+ /// @dev Gets all authorized addresses.
+ /// @return Array of authorized addresses.
+ function getAuthorizedAddresses()
+ public
+ constant
+ returns (address[])
+ {
+ return authorities;
+ }
+}
diff --git a/packages/sol-doc/test/fixtures/contracts/TokenTransferProxyNoDevdoc.sol b/packages/sol-doc/test/fixtures/contracts/TokenTransferProxyNoDevdoc.sol
new file mode 100644
index 000000000..cc45a79e9
--- /dev/null
+++ b/packages/sol-doc/test/fixtures/contracts/TokenTransferProxyNoDevdoc.sol
@@ -0,0 +1,100 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity ^0.4.14;
+
+import { Ownable } from "zeppelin-solidity/contracts/ownership/Ownable.sol";
+import { ERC20 as Token } from "zeppelin-solidity/contracts/token/ERC20/ERC20.sol";
+
+contract TokenTransferProxyNoDevdoc is Ownable {
+
+ modifier onlyAuthorized {
+ require(authorized[msg.sender]);
+ _;
+ }
+
+ modifier targetAuthorized(address target) {
+ require(authorized[target]);
+ _;
+ }
+
+ modifier targetNotAuthorized(address target) {
+ require(!authorized[target]);
+ _;
+ }
+
+ mapping (address => bool) public authorized;
+ address[] public authorities;
+
+ event LogAuthorizedAddressAdded(address indexed target, address indexed caller);
+ event LogAuthorizedAddressRemoved(address indexed target, address indexed caller);
+
+ /*
+ * Public functions
+ */
+
+ function addAuthorizedAddress(address target)
+ public
+ onlyOwner
+ targetNotAuthorized(target)
+ {
+ authorized[target] = true;
+ authorities.push(target);
+ LogAuthorizedAddressAdded(target, msg.sender);
+ }
+
+ function removeAuthorizedAddress(address target)
+ public
+ onlyOwner
+ targetAuthorized(target)
+ {
+ delete authorized[target];
+ for (uint i = 0; i < authorities.length; i++) {
+ if (authorities[i] == target) {
+ authorities[i] = authorities[authorities.length - 1];
+ authorities.length -= 1;
+ break;
+ }
+ }
+ LogAuthorizedAddressRemoved(target, msg.sender);
+ }
+
+ function transferFrom(
+ address token,
+ address from,
+ address to,
+ uint value)
+ public
+ onlyAuthorized
+ returns (bool)
+ {
+ return Token(token).transferFrom(from, to, value);
+ }
+
+ /*
+ * Public constant functions
+ */
+
+ function getAuthorizedAddresses()
+ public
+ constant
+ returns (address[])
+ {
+ return authorities;
+ }
+}
diff --git a/packages/sol-doc/test/solidity_doc_generator_test.ts b/packages/sol-doc/test/solidity_doc_generator_test.ts
new file mode 100644
index 000000000..f166fb143
--- /dev/null
+++ b/packages/sol-doc/test/solidity_doc_generator_test.ts
@@ -0,0 +1,273 @@
+import * as _ from 'lodash';
+
+import * as chai from 'chai';
+import 'mocha';
+
+import { DocAgnosticFormat, Event, SolidityMethod } from '@0xproject/types';
+
+import { SolDoc } from '../src/sol_doc';
+
+import { chaiSetup } from './util/chai_setup';
+
+chaiSetup.configure();
+const expect = chai.expect;
+const solDoc = new SolDoc();
+
+describe('#SolidityDocGenerator', () => {
+ it('should generate a doc object that matches the devdoc-free TokenTransferProxy fixture', async () => {
+ const doc = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
+ 'TokenTransferProxyNoDevdoc',
+ ]);
+ expect(doc).to.not.be.undefined();
+
+ verifyTokenTransferProxyABIIsDocumented(doc, 'TokenTransferProxyNoDevdoc');
+ });
+ const docPromises: Array<Promise<DocAgnosticFormat>> = [
+ solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`),
+ solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, []),
+ ];
+ docPromises.forEach(docPromise => {
+ it('should generate a doc object that matches the TokenTransferProxy fixture with its dependencies', async () => {
+ const doc = await docPromise;
+ expect(doc).to.not.be.undefined();
+
+ verifyTokenTransferProxyAndDepsABIsAreDocumented(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);
+ });
+ });
+ it('should generate a doc object that matches the TokenTransferProxy fixture', async () => {
+ const doc: DocAgnosticFormat = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
+ 'TokenTransferProxy',
+ ]);
+ verifyTokenTransferProxyABIIsDocumented(doc, 'TokenTransferProxy');
+ });
+ describe('when processing all the permutations of devdoc stuff that we use in our contracts', () => {
+ let doc: DocAgnosticFormat;
+ before(async () => {
+ doc = await solDoc.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', () => {
+ expect(methodDoc.returnType.name).to.equal('int256');
+ });
+ it('return comment', () => {
+ expect(methodDoc.returnComment).to.equal('publicMethod @return');
+ });
+ });
+ 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', () => {
+ expect(methodDoc.returnType.name).to.equal('int256');
+ });
+ it('return comment', () => {
+ expect(methodDoc.returnComment).to.equal('externalMethod @return');
+ });
+ });
+ 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');
+ });
+ });
+ it('should document a method that returns multiple values', async () => {
+ const doc = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
+ 'MultipleReturnValues',
+ ]);
+ expect(doc.MultipleReturnValues).to.not.be.undefined();
+ expect(doc.MultipleReturnValues.methods).to.not.be.undefined();
+ let methodWithMultipleReturnValues: SolidityMethod | undefined;
+ for (const method of doc.MultipleReturnValues.methods) {
+ if (method.name === 'methodWithMultipleReturnValues') {
+ methodWithMultipleReturnValues = method;
+ }
+ }
+ if (_.isUndefined(methodWithMultipleReturnValues)) {
+ throw new Error('method should not be undefined');
+ }
+ const returnType = methodWithMultipleReturnValues.returnType;
+ expect(returnType.typeDocType).to.equal('tuple');
+ if (_.isUndefined(returnType.tupleElements)) {
+ throw new Error('returnType.tupleElements should not be undefined');
+ }
+ expect(returnType.tupleElements.length).to.equal(2);
+ });
+ it('should document a method that has a struct param and return value', async () => {
+ const doc = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
+ 'StructParamAndReturn',
+ ]);
+ expect(doc.StructParamAndReturn).to.not.be.undefined();
+ expect(doc.StructParamAndReturn.methods).to.not.be.undefined();
+ let methodWithStructParamAndReturn: SolidityMethod | undefined;
+ for (const method of doc.StructParamAndReturn.methods) {
+ if (method.name === 'methodWithStructParamAndReturn') {
+ methodWithStructParamAndReturn = method;
+ }
+ }
+ if (_.isUndefined(methodWithStructParamAndReturn)) {
+ throw new Error('method should not be undefined');
+ }
+ /**
+ * Solc maps devDoc comments to methods using a method signature. If we incorrectly
+ * generate the methodSignatures, the devDoc comments won't be correctly associated
+ * with their methods and they won't show up in the output. By checking that the comments
+ * are included for a method with structs as params/returns, we are sure that the methodSignature
+ * generation is correct for this case.
+ */
+ expect(methodWithStructParamAndReturn.comment).to.be.equal('DEV_COMMENT');
+ expect(methodWithStructParamAndReturn.returnComment).to.be.equal('RETURN_COMMENT');
+ expect(methodWithStructParamAndReturn.parameters[0].comment).to.be.equal('STUFF_COMMENT');
+ });
+ it('should document the structs included in a contract', async () => {
+ const doc = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
+ 'StructParamAndReturn',
+ ]);
+ expect(doc.structs).to.not.be.undefined();
+ expect(doc.structs.types.length).to.be.equal(1);
+ });
+});
+
+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);
+}
+
+function verifyTokenTransferProxyAndDepsABIsAreDocumented(doc: DocAgnosticFormat, contractName: string): void {
+ verifyTokenTransferProxyABIIsDocumented(doc, contractName);
+
+ 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();
+}
diff --git a/packages/sol-doc/test/util/chai_setup.ts b/packages/sol-doc/test/util/chai_setup.ts
new file mode 100644
index 000000000..1a8733093
--- /dev/null
+++ b/packages/sol-doc/test/util/chai_setup.ts
@@ -0,0 +1,13 @@
+import * as chai from 'chai';
+import chaiAsPromised = require('chai-as-promised');
+import ChaiBigNumber = require('chai-bignumber');
+import * as dirtyChai from 'dirty-chai';
+
+export const chaiSetup = {
+ configure(): void {
+ chai.config.includeStack = true;
+ chai.use(ChaiBigNumber());
+ chai.use(dirtyChai);
+ chai.use(chaiAsPromised);
+ },
+};