aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorF. Eugene Aumson <gene@aumson.org>2018-08-29 23:01:04 +0800
committerF. Eugene Aumson <gene@aumson.org>2018-08-31 21:12:27 +0800
commit823b6c4d7df56e6bc517b72878fb1ff5823a5b6f (patch)
treed8a13f5ed7790c482437491071abd62e3c282512
parent8d122006baa7efdf8bd7a7df7b140a66c7d988af (diff)
downloaddexon-sol-tools-823b6c4d7df56e6bc517b72878fb1ff5823a5b6f.tar.gz
dexon-sol-tools-823b6c4d7df56e6bc517b72878fb1ff5823a5b6f.tar.zst
dexon-sol-tools-823b6c4d7df56e6bc517b72878fb1ff5823a5b6f.zip
transform solc's ABI output into doc types
-rw-r--r--packages/sol-doc/src/index.ts2
-rw-r--r--packages/sol-doc/src/solidity_doc_generator.ts296
-rw-r--r--packages/sol-doc/test/solidity_doc_generator_test.ts52
3 files changed, 284 insertions, 66 deletions
diff --git a/packages/sol-doc/src/index.ts b/packages/sol-doc/src/index.ts
index dc88fae90..03f3c9de6 100644
--- a/packages/sol-doc/src/index.ts
+++ b/packages/sol-doc/src/index.ts
@@ -1 +1 @@
-export { SolidityDocGenerator } from './solidity_doc_generator';
+export { generateSolDocAsync } from './solidity_doc_generator';
diff --git a/packages/sol-doc/src/solidity_doc_generator.ts b/packages/sol-doc/src/solidity_doc_generator.ts
index 95c89b2e5..312124ca1 100644
--- a/packages/sol-doc/src/solidity_doc_generator.ts
+++ b/packages/sol-doc/src/solidity_doc_generator.ts
@@ -1,76 +1,252 @@
import * as _ from 'lodash';
-import { MethodAbi } from 'ethereum-types';
+import {
+ AbiDefinition,
+ ConstructorAbi,
+ DataItem,
+ DevdocOutput,
+ EventAbi,
+ FallbackAbi,
+ MethodAbi,
+ StandardContractOutput,
+} from 'ethereum-types';
import { Compiler, CompilerOptions } from '@0xproject/sol-compiler';
-import { DocAgnosticFormat } from '@0xproject/types';
+import {
+ DocAgnosticFormat,
+ DocSection,
+ Event,
+ EventArg,
+ Parameter,
+ SolidityMethod,
+ Type,
+ TypeDocTypes,
+} from '@0xproject/types';
import { logUtils } from '@0xproject/utils';
/**
- * Compiles solidity files to both their ABI and devdoc outputs, and transforms
- * those outputs into the types that feed into documentation generation tools.
+ * Invoke the Solidity compiler and transform its ABI and devdoc outputs into
+ * the types that are used as input to documentation generation tools.
+ * @param contractsToCompile list of contracts for which to generate doc objects
+ * @param contractsDir the directory in which to find the `contractsToCompile` as well as their dependencies.
+ * @return doc object for use with documentation generation tools.
*/
-export class SolidityDocGenerator {
- private readonly _compilerOptions: CompilerOptions;
- /**
- * Instantiate the generator.
- * @param contractsDir the directory in which to find the contracts to be compiled
- */
- constructor(contractsDir: string) {
- // instantiate sol-compiler, passing in options to say we want abi and devdoc
- this._compilerOptions = {
- contractsDir,
- contracts: '*',
- compilerSettings: {
- outputSelection: {
- ['*']: {
- ['*']: ['abi', 'devdoc'],
- },
+export async function generateSolDocAsync(
+ contractsToCompile: string[],
+ contractsDir: string,
+): Promise<DocAgnosticFormat> {
+ const doc: DocAgnosticFormat = {};
+
+ const compilerOptions = _makeCompilerOptions(contractsToCompile, contractsDir);
+ const compiler = new Compiler(compilerOptions);
+ const compilerOutputs = await compiler.getCompilerOutputsAsync();
+ for (const compilerOutput of compilerOutputs) {
+ const contractFileNames = _.keys(compilerOutput.contracts);
+ for (const contractFileName of contractFileNames) {
+ const contractNameToOutput = compilerOutput.contracts[contractFileName];
+
+ const contractNames = _.keys(contractNameToOutput);
+ for (const contractName of contractNames) {
+ const compiledContract = contractNameToOutput[contractName];
+ if (_.isUndefined(compiledContract.abi)) {
+ throw new Error('compiled contract did not contain ABI output');
+ }
+ doc[contractName] = _genDocSection(compiledContract);
+ }
+ }
+ }
+
+ return doc;
+}
+
+function _makeCompilerOptions(contractsToCompile: string[], contractsDir: string): CompilerOptions {
+ const compilerOptions: CompilerOptions = {
+ contractsDir,
+ contracts: '*',
+ compilerSettings: {
+ outputSelection: {
+ ['*']: {
+ ['*']: ['abi', 'devdoc'],
},
},
- };
+ },
+ };
+
+ const shouldOverrideCatchAllContractsConfig = !_.isUndefined(contractsToCompile);
+ if (shouldOverrideCatchAllContractsConfig) {
+ compilerOptions.contracts = contractsToCompile;
}
- /**
- * Invoke the compiler and transform its outputs.
- * @param contractsToCompile list of contracts for which to generate doc objects
- * @return doc objects for use with documentation generation tools.
- */
- public async generateAsync(contractsToCompile: string[]): Promise<DocAgnosticFormat> {
- const shouldOverrideCatchAllContractsConfig = !_.isUndefined(contractsToCompile);
- if (shouldOverrideCatchAllContractsConfig) {
- this._compilerOptions.contracts = contractsToCompile;
- }
- const doc: DocAgnosticFormat = {};
-
- const compiler = new Compiler(this._compilerOptions);
- const compilerOutputs = await compiler.getCompilerOutputsAsync();
- for (const compilerOutput of compilerOutputs) {
- const contractFileNames = _.keys(compilerOutput.contracts);
- for (const contractFileName of contractFileNames) {
- const contractNameToOutput = compilerOutput.contracts[contractFileName];
-
- const contractNames = _.keys(contractNameToOutput);
- for (const contractName of contractNames) {
- const compiledContract = contractNameToOutput[contractName];
- if (_.isUndefined(compiledContract.abi)) {
- throw new Error('compiled contract did not contain ABI output.');
- }
- if (_.isUndefined(compiledContract.devdoc)) {
- throw new Error('compiled contract did not contain devdoc output.');
- }
-
- logUtils.log(
- `TODO: extract data from ${contractName}'s abi (eg name, which is "${
- (compiledContract.abi[0] as MethodAbi).name
- }", etc) and devdoc (eg title, which is "${
- compiledContract.devdoc.title
- }") outputs, and insert it into \`doc\``,
- );
- }
- }
+ return compilerOptions;
+}
+
+function _genDocSection(compiledContract: StandardContractOutput): DocSection {
+ const docSection: DocSection = {
+ comment: _.isUndefined(compiledContract.devdoc) ? '' : compiledContract.devdoc.title,
+ constructors: [],
+ methods: [],
+ properties: [],
+ types: [],
+ functions: [],
+ events: [],
+ };
+
+ for (const abiDefinition of compiledContract.abi) {
+ switch (abiDefinition.type) {
+ case 'constructor':
+ docSection.constructors.push(_genConstructorDoc(abiDefinition, compiledContract.devdoc));
+ break;
+ case 'event':
+ (docSection.events as Event[]).push(_genEventDoc(abiDefinition));
+ // note that we're not sending devdoc to _genEventDoc().
+ // that's because the type of the events array doesn't have any fields for documentation!
+ break;
+ case 'function':
+ docSection.methods.push(_genMethodDoc(abiDefinition, compiledContract.devdoc));
+ break;
+ default:
+ throw new Error(`unknown and unsupported AbiDefinition type '${abiDefinition.type}'`);
}
+ }
+
+ return docSection;
+}
+
+function _genConstructorDoc(abiDefinition: ConstructorAbi, devdocIfExists: DevdocOutput | undefined): SolidityMethod {
+ const { parameters, methodSignature } = _genMethodParamsDoc(
+ '', // TODO: update depending on how constructors are keyed in devdoc
+ abiDefinition.inputs,
+ devdocIfExists,
+ );
+
+ let comment;
+ // TODO: use methodSignature as the key to abiEntry.devdoc.methods, and
+ // from that object extract the "details" (comment) property
+ comment = 'something from devdoc';
+
+ return {
+ isConstructor: true,
+ name: '', // sad we have to specify this
+ callPath: '', // TODO: wtf is this?
+ parameters,
+ returnType: { name: '', typeDocType: TypeDocTypes.Intrinsic }, // sad we have to specify this
+ isConstant: false, // constructors are non-const by their nature, right?
+ isPayable: abiDefinition.payable,
+ comment,
+ };
+}
+
+function _genMethodDoc(
+ abiDefinition: MethodAbi | FallbackAbi,
+ devdocIfExists: DevdocOutput | undefined,
+): SolidityMethod {
+ const name = abiDefinition.type === 'fallback' ? '' : abiDefinition.name;
- return doc;
+ const { parameters, methodSignature } =
+ abiDefinition.type === 'fallback'
+ ? { parameters: [], methodSignature: `${name}()` }
+ : _genMethodParamsDoc(name, abiDefinition.inputs, devdocIfExists);
+
+ let comment;
+ // TODO: use methodSignature as the key to abiEntry.devdoc.methods, and
+ // from that object extract the "details" (comment) property
+ comment = 'something from devdoc';
+
+ const returnType =
+ abiDefinition.type === 'fallback'
+ ? { name: '', typeDocType: TypeDocTypes.Intrinsic }
+ : _genMethodReturnTypeDoc(abiDefinition.outputs, methodSignature, devdocIfExists);
+
+ const isConstant = abiDefinition.type === 'fallback' ? true : abiDefinition.constant;
+ // TODO: determine whether fallback functions are always constant, as coded just above
+
+ return {
+ isConstructor: false,
+ name,
+ callPath: '', // TODO: wtf is this?
+ parameters,
+ returnType,
+ isConstant,
+ isPayable: abiDefinition.payable,
+ comment,
+ };
+}
+
+function _genEventDoc(abiDefinition: EventAbi): Event {
+ const eventDoc: Event = {
+ name: abiDefinition.name,
+ eventArgs: _genEventArgsDoc(abiDefinition.inputs, undefined),
+ };
+ return eventDoc;
+}
+
+function _genEventArgsDoc(args: DataItem[], devdocIfExists: DevdocOutput | undefined): EventArg[] {
+ const eventArgsDoc: EventArg[] = [];
+
+ for (const arg of args) {
+ const name = arg.name;
+
+ const type: Type = {
+ name: arg.type,
+ typeDocType: TypeDocTypes.Intrinsic,
+ };
+
+ const eventArgDoc: EventArg = {
+ isIndexed: false, // TODO: wtf is this?
+ name,
+ type,
+ };
+
+ eventArgsDoc.push(eventArgDoc);
+ }
+ return eventArgsDoc;
+}
+
+/**
+ * Extract documentation for each method paramater from @param params.
+ * TODO: Then, use @param name, along with the types of the method
+ * parameters, to form a method signature. That signature is the key to
+ * the method documentation held in @param devdocIfExists.
+ */
+function _genMethodParamsDoc(
+ name: string,
+ params: DataItem[],
+ devdocIfExists: DevdocOutput | undefined,
+): { parameters: Parameter[]; methodSignature: string } {
+ const parameters: Parameter[] = [];
+ for (const input of params) {
+ const parameter: Parameter = {
+ name: input.name,
+ comment: '', // TODO: get from devdoc. see comment below.
+ isOptional: false, // Unsupported in Solidity, until resolution of https://github.com/ethereum/solidity/issues/232
+ type: { name: input.type, typeDocType: TypeDocTypes.Intrinsic },
+ };
+ parameters.push(parameter);
+ }
+ // TODO: use methodSignature as the key to abiEntry.devdoc.methods, and
+ // from that object extract the "details" (comment) property
+ return { parameters, methodSignature: '' };
+}
+
+function _genMethodReturnTypeDoc(
+ outputs: DataItem[],
+ methodSignature: string,
+ devdocIfExists: DevdocOutput | undefined,
+): Type {
+ const methodReturnTypeDoc: Type = {
+ name: '',
+ typeDocType: TypeDocTypes.Intrinsic,
+ tupleElements: undefined,
+ };
+ if (outputs.length > 1) {
+ methodReturnTypeDoc.typeDocType = TypeDocTypes.Tuple;
+ methodReturnTypeDoc.tupleElements = [];
+ for (const output of outputs) {
+ methodReturnTypeDoc.tupleElements.push({ name: output.name, typeDocType: TypeDocTypes.Intrinsic });
+ }
+ } else if (outputs.length === 1) {
+ methodReturnTypeDoc.typeDocType = TypeDocTypes.Intrinsic;
+ methodReturnTypeDoc.name = outputs[0].name;
}
+ return methodReturnTypeDoc;
}
diff --git a/packages/sol-doc/test/solidity_doc_generator_test.ts b/packages/sol-doc/test/solidity_doc_generator_test.ts
index 1df3e5b44..df6ad8e54 100644
--- a/packages/sol-doc/test/solidity_doc_generator_test.ts
+++ b/packages/sol-doc/test/solidity_doc_generator_test.ts
@@ -1,7 +1,11 @@
+import * as _ from 'lodash';
+
import * as chai from 'chai';
import 'mocha';
-import { SolidityDocGenerator } from '../src/solidity_doc_generator';
+import { DocSection } from '@0xproject/types';
+
+import { generateSolDocAsync } from '../src/solidity_doc_generator';
import { chaiSetup } from './util/chai_setup';
@@ -9,11 +13,49 @@ chaiSetup.configure();
const expect = chai.expect;
describe('#SolidityDocGenerator', () => {
- it('should generate', async () => {
- const generator = new SolidityDocGenerator(`${__dirname}/../../test/fixtures/contracts`);
-
- const doc = await generator.generateAsync(['TokenTransferProxy']);
+ it('should generate a doc object that matches the TokenTransferProxy fixture', async () => {
+ const doc = await generateSolDocAsync(['TokenTransferProxy'], `${__dirname}/../../test/fixtures/contracts`);
expect(doc).to.not.be.undefined();
+
+ const tokenTransferProxyConstructorCount = 0;
+ const tokenTransferProxyMethodCount = 8;
+ const tokenTransferProxyEventCount = 3;
+ expect(doc.TokenTransferProxy.constructors.length).to.equal(tokenTransferProxyConstructorCount);
+ expect(doc.TokenTransferProxy.methods.length).to.equal(tokenTransferProxyMethodCount);
+ if (_.isUndefined(doc.TokenTransferProxy.events)) {
+ throw new Error('events should never be undefined');
+ }
+ expect(doc.TokenTransferProxy.events.length).to.equal(tokenTransferProxyEventCount);
+
+ 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);
+
+ 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);
+
+ 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);
});
});