diff options
author | Fabio Berger <me@fabioberger.com> | 2018-09-29 00:20:24 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-09-29 00:20:24 +0800 |
commit | 9ae60d0abe0ea32eb9dbfc48a9168d1996c674a0 (patch) | |
tree | cfdf41b5727cfeeea3b4586478078501cb92c825 /packages/sol-doc/src/solidity_doc_generator.ts | |
parent | 005a2e12bac3e0ca8762f627ec5e9df6d2991d6e (diff) | |
download | dexon-sol-tools-9ae60d0abe0ea32eb9dbfc48a9168d1996c674a0.tar.gz dexon-sol-tools-9ae60d0abe0ea32eb9dbfc48a9168d1996c674a0.tar.zst dexon-sol-tools-9ae60d0abe0ea32eb9dbfc48a9168d1996c674a0.zip |
Export class instead of function
Diffstat (limited to 'packages/sol-doc/src/solidity_doc_generator.ts')
-rw-r--r-- | packages/sol-doc/src/solidity_doc_generator.ts | 516 |
1 files changed, 0 insertions, 516 deletions
diff --git a/packages/sol-doc/src/solidity_doc_generator.ts b/packages/sol-doc/src/solidity_doc_generator.ts deleted file mode 100644 index 424577411..000000000 --- a/packages/sol-doc/src/solidity_doc_generator.ts +++ /dev/null @@ -1,516 +0,0 @@ -import * as path from 'path'; - -import { - AbiDefinition, - ConstructorAbi, - DataItem, - DevdocOutput, - EventAbi, - EventParameter, - FallbackAbi, - MethodAbi, - StandardContractOutput, -} from 'ethereum-types'; -import ethUtil = require('ethereumjs-util'); -import * as _ from 'lodash'; - -import { Compiler, CompilerOptions } from '@0xproject/sol-compiler'; -import { - CustomType, - CustomTypeChild, - DocAgnosticFormat, - DocSection, - Event, - EventArg, - Parameter, - SolidityMethod, - Type, - TypeDocTypes, -} from '@0xproject/types'; - -// Unforunately, the only way to currently retrieve the declared structs within Solidity contracts -// is to tease them out of the params/return values included in the ABI. These structures do -// not include the structs actual name, so we need a mapping to assign the proper name to a -// struct. If the name is not in this mapping, the structs name will default to the param/return value -// name (which mostly coincide). -const customTypeHashToName: { [hash: string]: string } = { - '52d4a768701076c7bac06e386e430883975eb398732eccba797fd09dd064a60e': 'Order', - '46f7e8c4d144d11a72ce5338458ea37b933500d7a65e740cbca6d16e350eaa48': 'FillResult', - c22239cf0d29df1e6cf1be54f21692a8c0b3a48b9367540d4ffff4608b331ce9: 'OrderInfo', - c21e9ff31a30941c22e1cb43752114bb467c34dea58947f98966c9030fc8e4a9: 'TraderInfo', - '07c2bddc165e0b5005e6244dd4a9771fa61c78c4f42abd687d57567b0768136c': 'MatchedFillResult', -}; - -/** - * Invoke the Solidity compiler and transform its ABI and devdoc outputs into a - * JSON format easily consumed by documentation rendering tools. - * @param contractsToDocument 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 async function generateSolDocAsync( - contractsDir: string, - contractsToDocument?: string[], -): Promise<DocAgnosticFormat> { - const docWithDependencies: DocAgnosticFormat = {}; - const compilerOptions = _makeCompilerOptions(contractsDir, contractsToDocument); - const compiler = new Compiler(compilerOptions); - const compilerOutputs = await compiler.getCompilerOutputsAsync(); - let structs: CustomType[] = []; - 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'); - } - docWithDependencies[contractName] = _genDocSection(compiledContract, contractName); - structs = [...structs, ..._extractStructs(compiledContract)]; - } - } - } - structs = _dedupStructs(structs); - structs = _overwriteStructNames(structs); - - let doc: DocAgnosticFormat = {}; - if (_.isUndefined(contractsToDocument) || contractsToDocument.length === 0) { - doc = docWithDependencies; - } else { - for (const contractToDocument of contractsToDocument) { - const contractBasename = path.basename(contractToDocument); - const contractName = - contractBasename.lastIndexOf('.sol') === -1 - ? contractBasename - : contractBasename.substring(0, contractBasename.lastIndexOf('.sol')); - doc[contractName] = docWithDependencies[contractName]; - } - } - - if (structs.length > 0) { - doc.structs = { - comment: '', - constructors: [], - methods: [], - properties: [], - types: structs, - functions: [], - events: [], - }; - } - - return doc; -} - -function _makeCompilerOptions(contractsDir: string, contractsToCompile?: string[]): CompilerOptions { - const compilerOptions: CompilerOptions = { - contractsDir, - contracts: '*', - compilerSettings: { - outputSelection: { - ['*']: { - ['*']: ['abi', 'devdoc'], - }, - }, - }, - }; - - const shouldOverrideCatchAllContractsConfig = !_.isUndefined(contractsToCompile) && contractsToCompile.length > 0; - if (shouldOverrideCatchAllContractsConfig) { - compilerOptions.contracts = contractsToCompile; - } - - return compilerOptions; -} - -function _extractStructs(compiledContract: StandardContractOutput): CustomType[] { - let customTypes: CustomType[] = []; - for (const abiDefinition of compiledContract.abi) { - let types: CustomType[] = []; - switch (abiDefinition.type) { - case 'constructor': { - types = _getStructsAsCustomTypes(abiDefinition); - break; - } - case 'function': { - types = _getStructsAsCustomTypes(abiDefinition); - break; - } - case 'event': - case 'fallback': - // No types exist - break; - default: - throw new Error( - `unknown and unsupported AbiDefinition type '${(abiDefinition as AbiDefinition).type}'`, - ); - } - customTypes = [...customTypes, ...types]; - } - return customTypes; -} - -function _genDocSection(compiledContract: StandardContractOutput, contractName: string): 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(contractName, 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; - case 'fallback': - docSection.methods.push(_genFallbackDoc(abiDefinition, compiledContract.devdoc)); - break; - default: - throw new Error( - `unknown and unsupported AbiDefinition type '${(abiDefinition as AbiDefinition).type}'`, - ); - } - } - - return docSection; -} - -function _genConstructorDoc( - contractName: string, - abiDefinition: ConstructorAbi, - devdocIfExists: DevdocOutput | undefined, -): SolidityMethod { - const { parameters, methodSignature } = _genMethodParamsDoc('', abiDefinition.inputs, devdocIfExists); - - const comment = _devdocMethodDetailsIfExist(methodSignature, devdocIfExists); - - const constructorDoc: SolidityMethod = { - isConstructor: true, - name: contractName, - callPath: '', - parameters, - returnType: { name: contractName, typeDocType: TypeDocTypes.Reference }, // sad we have to specify this - isConstant: false, - isPayable: abiDefinition.payable, - comment, - }; - - return constructorDoc; -} - -function _devdocMethodDetailsIfExist( - methodSignature: string, - devdocIfExists: DevdocOutput | undefined, -): string | undefined { - let details; - if (!_.isUndefined(devdocIfExists)) { - const devdocMethodsIfExist = devdocIfExists.methods; - if (!_.isUndefined(devdocMethodsIfExist)) { - const devdocMethodIfExists = devdocMethodsIfExist[methodSignature]; - if (!_.isUndefined(devdocMethodIfExists)) { - const devdocMethodDetailsIfExist = devdocMethodIfExists.details; - if (!_.isUndefined(devdocMethodDetailsIfExist)) { - details = devdocMethodDetailsIfExist; - } - } - } - } - return details; -} - -function _genFallbackDoc(abiDefinition: FallbackAbi, devdocIfExists: DevdocOutput | undefined): SolidityMethod { - const methodSignature = `()`; - const comment = _devdocMethodDetailsIfExist(methodSignature, devdocIfExists); - - const returnComment = - _.isUndefined(devdocIfExists) || _.isUndefined(devdocIfExists.methods[methodSignature]) - ? undefined - : devdocIfExists.methods[methodSignature].return; - - const methodDoc: SolidityMethod = { - isConstructor: false, - name: 'fallback', - callPath: '', - parameters: [], - returnType: { name: 'void', typeDocType: TypeDocTypes.Intrinsic }, - returnComment, - isConstant: true, - isPayable: abiDefinition.payable, - isFallback: true, - comment: _.isEmpty(comment) - ? 'The default fallback function. It is executed on a call to the contract if none of the other functions match the given function identifier (or if no data was supplied at all).' - : comment, - }; - return methodDoc; -} - -function _genMethodDoc(abiDefinition: MethodAbi, devdocIfExists: DevdocOutput | undefined): SolidityMethod { - const name = abiDefinition.name; - const { parameters, methodSignature } = _genMethodParamsDoc(name, abiDefinition.inputs, devdocIfExists); - const devDocComment = _devdocMethodDetailsIfExist(methodSignature, devdocIfExists); - const returnType = _genMethodReturnTypeDoc(abiDefinition.outputs); - const returnComment = - _.isUndefined(devdocIfExists) || _.isUndefined(devdocIfExists.methods[methodSignature]) - ? undefined - : devdocIfExists.methods[methodSignature].return; - - const hasNoNamedParameters = _.isUndefined(_.find(parameters, p => !_.isEmpty(p.name))); - const isGeneratedGetter = hasNoNamedParameters; - const comment = - _.isEmpty(devDocComment) && isGeneratedGetter - ? `This is an auto-generated accessor method of the '${name}' contract instance variable.` - : devDocComment; - const methodDoc: SolidityMethod = { - isConstructor: false, - name, - callPath: '', - parameters, - returnType, - returnComment, - isConstant: abiDefinition.constant, - isPayable: abiDefinition.payable, - comment, - }; - return methodDoc; -} - -function _genEventDoc(abiDefinition: EventAbi): Event { - const eventDoc: Event = { - name: abiDefinition.name, - eventArgs: _genEventArgsDoc(abiDefinition.inputs, undefined), - }; - return eventDoc; -} - -function _genEventArgsDoc(args: EventParameter[], 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: arg.indexed, - name, - type, - }; - - eventArgsDoc.push(eventArgDoc); - } - return eventArgsDoc; -} - -/** - * Extract documentation for each method parameter from @param params. - */ -function _genMethodParamsDoc( - name: string, - abiParams: DataItem[], - devdocIfExists: DevdocOutput | undefined, -): { parameters: Parameter[]; methodSignature: string } { - const parameters: Parameter[] = []; - for (const abiParam of abiParams) { - const type = _getTypeFromDataItem(abiParam); - - const parameter: Parameter = { - name: abiParam.name, - comment: '<No comment>', - isOptional: false, // Unsupported in Solidity, until resolution of https://github.com/ethereum/solidity/issues/232 - type, - }; - parameters.push(parameter); - } - - const methodSignature = `${name}(${abiParams - .map(abiParam => { - if (!_.startsWith(abiParam.type, 'tuple')) { - return abiParam.type; - } else { - // Need to expand tuples: - // E.g: fillOrder(tuple,uint256,bytes) -> fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes) - const isArray = _.endsWith(abiParam.type, '[]'); - const expandedTypes = _.map(abiParam.components, c => c.type); - const type = `(${expandedTypes.join(',')})${isArray ? '[]' : ''}`; - return type; - } - }) - .join(',')})`; - - if (!_.isUndefined(devdocIfExists)) { - const devdocMethodIfExists = devdocIfExists.methods[methodSignature]; - if (!_.isUndefined(devdocMethodIfExists)) { - const devdocParamsIfExist = devdocMethodIfExists.params; - if (!_.isUndefined(devdocParamsIfExist)) { - for (const parameter of parameters) { - parameter.comment = devdocParamsIfExist[parameter.name]; - } - } - } - } - - return { parameters, methodSignature }; -} - -function _genMethodReturnTypeDoc(outputs: DataItem[]): Type { - if (outputs.length > 1) { - const type: Type = { - name: '', - typeDocType: TypeDocTypes.Tuple, - tupleElements: [], - }; - for (const output of outputs) { - const tupleType = _getTypeFromDataItem(output); - (type.tupleElements as Type[]).push(tupleType); - } - return type; - } else if (outputs.length === 1) { - const output = outputs[0]; - const type = _getTypeFromDataItem(output); - return type; - } else { - const type: Type = { - name: 'void', - typeDocType: TypeDocTypes.Intrinsic, - }; - return type; - } -} - -function _capitalize(text: string): string { - return `${text.charAt(0).toUpperCase()}${text.slice(1)}`; -} - -function _dedupStructs(customTypes: CustomType[]): CustomType[] { - const uniqueCustomTypes: CustomType[] = []; - const seenTypes: { [hash: string]: boolean } = {}; - _.each(customTypes, customType => { - const hash = _generateCustomTypeHash(customType); - if (!seenTypes[hash]) { - uniqueCustomTypes.push(customType); - seenTypes[hash] = true; - } - }); - return uniqueCustomTypes; -} - -function _overwriteStructNames(customTypes: CustomType[]): CustomType[] { - const localCustomTypes = _.cloneDeep(customTypes); - _.each(localCustomTypes, customType => { - const hash = _generateCustomTypeHash(customType); - if (!_.isUndefined(customTypeHashToName[hash])) { - customType.name = customTypeHashToName[hash]; - } - }); - return localCustomTypes; -} - -function _generateCustomTypeHash(customType: CustomType): string { - const customTypeWithoutName = _.cloneDeep(customType); - delete customTypeWithoutName.name; - const customTypeWithoutNameStr = JSON.stringify(customTypeWithoutName); - const hash = ethUtil.sha256(customTypeWithoutNameStr).toString('hex'); - return hash; -} - -function _getStructsAsCustomTypes(abiDefinition: AbiDefinition): CustomType[] { - const customTypes: CustomType[] = []; - // We cast to `any` here because we do not know yet if this type of abiDefinition contains - // an `input` key - if (!_.isUndefined((abiDefinition as any).inputs)) { - const methodOrConstructorAbi = abiDefinition as MethodAbi | ConstructorAbi; - _.each(methodOrConstructorAbi.inputs, input => { - if (!_.isUndefined(input.components)) { - const customType = _getCustomTypeFromDataItem(input); - customTypes.push(customType); - } - }); - } - if (!_.isUndefined((abiDefinition as any).outputs)) { - const methodAbi = abiDefinition as MethodAbi; - _.each(methodAbi.outputs, output => { - if (!_.isUndefined(output.components)) { - const customType = _getCustomTypeFromDataItem(output); - customTypes.push(customType); - } - }); - } - return customTypes; -} - -function _getCustomTypeFromDataItem(inputOrOutput: DataItem): CustomType { - const customType: CustomType = { - name: _.capitalize(inputOrOutput.name), - kindString: 'Interface', - children: [], - }; - _.each(inputOrOutput.components, (component: DataItem) => { - const childType = _getTypeFromDataItem(component); - const customTypeChild = { - name: component.name, - type: childType, - }; - // (fabio): Not sure why this type casting is necessary. Seems TS doesn't - // deduce that `customType.children` cannot be undefined anymore after being - // set to `[]` above. - (customType.children as CustomTypeChild[]).push(customTypeChild); - }); - return customType; -} - -function _getNameFromDataItemIfExists(dataItem: DataItem): string | undefined { - if (_.isUndefined(dataItem.components)) { - return undefined; - } - const customType = _getCustomTypeFromDataItem(dataItem); - const hash = _generateCustomTypeHash(customType); - if (_.isUndefined(customTypeHashToName[hash])) { - return undefined; - } - return customTypeHashToName[hash]; -} - -function _getTypeFromDataItem(dataItem: DataItem): Type { - const typeDocType = !_.isUndefined(dataItem.components) ? TypeDocTypes.Reference : TypeDocTypes.Intrinsic; - let typeName: string; - if (typeDocType === TypeDocTypes.Reference) { - const nameIfExists = _getNameFromDataItemIfExists(dataItem); - typeName = _.isUndefined(nameIfExists) ? _capitalize(dataItem.name) : nameIfExists; - } else { - typeName = dataItem.type; - } - - const isArrayType = _.endsWith(dataItem.type, '[]'); - let type: Type; - if (isArrayType) { - // tslint:disable-next-line:custom-no-magic-numbers - typeName = typeDocType === TypeDocTypes.Intrinsic ? typeName.slice(0, -2) : typeName; - type = { - elementType: { name: typeName, typeDocType }, - typeDocType: TypeDocTypes.Array, - name: '', - }; - } else { - type = { name: typeName, typeDocType }; - } - return type; -} -// tslint:disable:max-file-line-count |