Export class instead of function
This commit is contained in:
@@ -3,7 +3,7 @@ import * as yargs from 'yargs';
|
||||
|
||||
import { logUtils } from '@0xproject/utils';
|
||||
|
||||
import { generateSolDocAsync } from './solidity_doc_generator';
|
||||
import { SolDoc } from './sol_doc';
|
||||
|
||||
const JSON_TAB_WIDTH = 4;
|
||||
|
||||
@@ -20,7 +20,20 @@ const JSON_TAB_WIDTH = 4;
|
||||
.demandOption('contracts-dir')
|
||||
.array('contracts')
|
||||
.help().argv;
|
||||
const doc = await generateSolDocAsync(argv.contractsDir, argv.contracts);
|
||||
// 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': 'MatchedFillResults',
|
||||
};
|
||||
const solDoc = new SolDoc();
|
||||
const doc = await solDoc.generateSolDocAsync(argv.contractsDir, argv.contracts, customTypeHashToName);
|
||||
process.stdout.write(JSON.stringify(doc, null, JSON_TAB_WIDTH));
|
||||
})().catch(err => {
|
||||
logUtils.warn(err);
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { generateSolDocAsync } from './solidity_doc_generator';
|
||||
export { SolDoc } from './sol_doc';
|
||||
|
||||
500
packages/sol-doc/src/sol_doc.ts
Normal file
500
packages/sol-doc/src/sol_doc.ts
Normal file
@@ -0,0 +1,500 @@
|
||||
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,
|
||||
ObjectMap,
|
||||
Parameter,
|
||||
SolidityMethod,
|
||||
Type,
|
||||
TypeDocTypes,
|
||||
} from '@0xproject/types';
|
||||
|
||||
export class SolDoc {
|
||||
private _customTypeHashToName: ObjectMap<string> | undefined;
|
||||
private static _genEventDoc(abiDefinition: EventAbi): Event {
|
||||
const eventDoc: Event = {
|
||||
name: abiDefinition.name,
|
||||
eventArgs: SolDoc._genEventArgsDoc(abiDefinition.inputs),
|
||||
};
|
||||
return eventDoc;
|
||||
}
|
||||
private static _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;
|
||||
}
|
||||
private static _genFallbackDoc(
|
||||
abiDefinition: FallbackAbi,
|
||||
devdocIfExists: DevdocOutput | undefined,
|
||||
): SolidityMethod {
|
||||
const methodSignature = `()`;
|
||||
const comment = SolDoc._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 fallback function. It is executed on a call to the contract if none of the other functions match the given public identifier (or if no data was supplied at all).'
|
||||
: comment,
|
||||
};
|
||||
return methodDoc;
|
||||
}
|
||||
private static _genEventArgsDoc(args: EventParameter[]): 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;
|
||||
}
|
||||
private static _dedupStructs(customTypes: CustomType[]): CustomType[] {
|
||||
const uniqueCustomTypes: CustomType[] = [];
|
||||
const seenTypes: { [hash: string]: boolean } = {};
|
||||
_.each(customTypes, customType => {
|
||||
const hash = SolDoc._generateCustomTypeHash(customType);
|
||||
if (!seenTypes[hash]) {
|
||||
uniqueCustomTypes.push(customType);
|
||||
seenTypes[hash] = true;
|
||||
}
|
||||
});
|
||||
return uniqueCustomTypes;
|
||||
}
|
||||
private static _capitalize(text: string): string {
|
||||
return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
|
||||
}
|
||||
private static _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;
|
||||
}
|
||||
private static _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;
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public async generateSolDocAsync(
|
||||
contractsDir: string,
|
||||
contractsToDocument?: string[],
|
||||
customTypeHashToName?: ObjectMap<string>,
|
||||
): Promise<DocAgnosticFormat> {
|
||||
this._customTypeHashToName = customTypeHashToName;
|
||||
const docWithDependencies: DocAgnosticFormat = {};
|
||||
const compilerOptions = SolDoc._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] = this._genDocSection(compiledContract, contractName);
|
||||
structs = [...structs, ...this._extractStructs(compiledContract)];
|
||||
}
|
||||
}
|
||||
}
|
||||
structs = SolDoc._dedupStructs(structs);
|
||||
structs = this._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: [],
|
||||
};
|
||||
}
|
||||
|
||||
delete this._customTypeHashToName; // Clean up instance state
|
||||
return doc;
|
||||
}
|
||||
private _getCustomTypeFromDataItem(inputOrOutput: DataItem): CustomType {
|
||||
const customType: CustomType = {
|
||||
name: _.capitalize(inputOrOutput.name),
|
||||
kindString: 'Interface',
|
||||
children: [],
|
||||
};
|
||||
_.each(inputOrOutput.components, (component: DataItem) => {
|
||||
const childType = this._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;
|
||||
}
|
||||
private _getNameFromDataItemIfExists(dataItem: DataItem): string | undefined {
|
||||
if (_.isUndefined(dataItem.components)) {
|
||||
return undefined;
|
||||
}
|
||||
const customType = this._getCustomTypeFromDataItem(dataItem);
|
||||
const hash = SolDoc._generateCustomTypeHash(customType);
|
||||
if (_.isUndefined(this._customTypeHashToName) || _.isUndefined(this._customTypeHashToName[hash])) {
|
||||
return undefined;
|
||||
}
|
||||
return this._customTypeHashToName[hash];
|
||||
}
|
||||
private _getTypeFromDataItem(dataItem: DataItem): Type {
|
||||
const typeDocType = !_.isUndefined(dataItem.components) ? TypeDocTypes.Reference : TypeDocTypes.Intrinsic;
|
||||
let typeName: string;
|
||||
if (typeDocType === TypeDocTypes.Reference) {
|
||||
const nameIfExists = this._getNameFromDataItemIfExists(dataItem);
|
||||
typeName = _.isUndefined(nameIfExists) ? SolDoc._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;
|
||||
}
|
||||
private _overwriteStructNames(customTypes: CustomType[], customTypeHashToName?: ObjectMap<string>): CustomType[] {
|
||||
if (_.isUndefined(customTypeHashToName)) {
|
||||
return customTypes;
|
||||
}
|
||||
const localCustomTypes = _.cloneDeep(customTypes);
|
||||
_.each(localCustomTypes, customType => {
|
||||
const hash = SolDoc._generateCustomTypeHash(customType);
|
||||
if (!_.isUndefined(this._customTypeHashToName) && !_.isUndefined(this._customTypeHashToName[hash])) {
|
||||
customType.name = this._customTypeHashToName[hash];
|
||||
}
|
||||
});
|
||||
return localCustomTypes;
|
||||
}
|
||||
private _extractStructs(compiledContract: StandardContractOutput): CustomType[] {
|
||||
let customTypes: CustomType[] = [];
|
||||
for (const abiDefinition of compiledContract.abi) {
|
||||
let types: CustomType[] = [];
|
||||
switch (abiDefinition.type) {
|
||||
case 'constructor': {
|
||||
types = this._getStructsAsCustomTypes(abiDefinition);
|
||||
break;
|
||||
}
|
||||
case 'function': {
|
||||
types = this._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;
|
||||
}
|
||||
private _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(
|
||||
this._genConstructorDoc(contractName, abiDefinition, compiledContract.devdoc),
|
||||
);
|
||||
break;
|
||||
case 'event':
|
||||
(docSection.events as Event[]).push(SolDoc._genEventDoc(abiDefinition));
|
||||
// note that we're not sending devdoc to this._genEventDoc().
|
||||
// that's because the type of the events array doesn't have any fields for documentation!
|
||||
break;
|
||||
case 'function':
|
||||
docSection.methods.push(this._genMethodDoc(abiDefinition, compiledContract.devdoc));
|
||||
break;
|
||||
case 'fallback':
|
||||
docSection.methods.push(SolDoc._genFallbackDoc(abiDefinition, compiledContract.devdoc));
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`unknown and unsupported AbiDefinition type '${(abiDefinition as AbiDefinition).type}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return docSection;
|
||||
}
|
||||
private _genConstructorDoc(
|
||||
contractName: string,
|
||||
abiDefinition: ConstructorAbi,
|
||||
devdocIfExists: DevdocOutput | undefined,
|
||||
): SolidityMethod {
|
||||
const { parameters, methodSignature } = this._genMethodParamsDoc('', abiDefinition.inputs, devdocIfExists);
|
||||
|
||||
const comment = SolDoc._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;
|
||||
}
|
||||
private _genMethodDoc(abiDefinition: MethodAbi, devdocIfExists: DevdocOutput | undefined): SolidityMethod {
|
||||
const name = abiDefinition.name;
|
||||
const { parameters, methodSignature } = this._genMethodParamsDoc(name, abiDefinition.inputs, devdocIfExists);
|
||||
const devDocComment = SolDoc._devdocMethodDetailsIfExist(methodSignature, devdocIfExists);
|
||||
const returnType = this._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;
|
||||
}
|
||||
/**
|
||||
* Extract documentation for each method parameter from @param params.
|
||||
*/
|
||||
private _genMethodParamsDoc(
|
||||
name: string,
|
||||
abiParams: DataItem[],
|
||||
devdocIfExists: DevdocOutput | undefined,
|
||||
): { parameters: Parameter[]; methodSignature: string } {
|
||||
const parameters: Parameter[] = [];
|
||||
for (const abiParam of abiParams) {
|
||||
const type = this._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 };
|
||||
}
|
||||
private _genMethodReturnTypeDoc(outputs: DataItem[]): Type {
|
||||
let type: Type;
|
||||
if (outputs.length > 1) {
|
||||
type = {
|
||||
name: '',
|
||||
typeDocType: TypeDocTypes.Tuple,
|
||||
tupleElements: [],
|
||||
};
|
||||
for (const output of outputs) {
|
||||
const tupleType = this._getTypeFromDataItem(output);
|
||||
(type.tupleElements as Type[]).push(tupleType);
|
||||
}
|
||||
return type;
|
||||
} else if (outputs.length === 1) {
|
||||
const output = outputs[0];
|
||||
type = this._getTypeFromDataItem(output);
|
||||
} else {
|
||||
type = {
|
||||
name: 'void',
|
||||
typeDocType: TypeDocTypes.Intrinsic,
|
||||
};
|
||||
}
|
||||
return type;
|
||||
}
|
||||
private _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 = this._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 = this._getCustomTypeFromDataItem(output);
|
||||
customTypes.push(customType);
|
||||
}
|
||||
});
|
||||
}
|
||||
return customTypes;
|
||||
}
|
||||
}
|
||||
// tslint:disable:max-file-line-count
|
||||
@@ -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
|
||||
@@ -5,16 +5,20 @@ import 'mocha';
|
||||
|
||||
import { DocAgnosticFormat, Event, SolidityMethod } from '@0xproject/types';
|
||||
|
||||
import { generateSolDocAsync } from '../src/solidity_doc_generator';
|
||||
import { SolDoc } from '../src/sol_doc';
|
||||
|
||||
import { chaiSetup } from './util/chai_setup';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
let solDoc: SolDoc;
|
||||
|
||||
describe('#SolidityDocGenerator', () => {
|
||||
before(() => {
|
||||
solDoc = new SolDoc();
|
||||
});
|
||||
it('should generate a doc object that matches the devdoc-free TokenTransferProxy fixture', async () => {
|
||||
const doc = await generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
|
||||
const doc = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
|
||||
'TokenTransferProxyNoDevdoc',
|
||||
]);
|
||||
expect(doc).to.not.be.undefined();
|
||||
@@ -22,8 +26,8 @@ describe('#SolidityDocGenerator', () => {
|
||||
verifyTokenTransferProxyABIIsDocumented(doc, 'TokenTransferProxyNoDevdoc');
|
||||
});
|
||||
const docPromises: Array<Promise<DocAgnosticFormat>> = [
|
||||
generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`),
|
||||
generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, []),
|
||||
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 () => {
|
||||
@@ -48,7 +52,7 @@ describe('#SolidityDocGenerator', () => {
|
||||
});
|
||||
});
|
||||
it('should generate a doc object that matches the TokenTransferProxy fixture', async () => {
|
||||
const doc: DocAgnosticFormat = await generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
|
||||
const doc: DocAgnosticFormat = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, [
|
||||
'TokenTransferProxy',
|
||||
]);
|
||||
verifyTokenTransferProxyABIIsDocumented(doc, 'TokenTransferProxy');
|
||||
@@ -56,7 +60,7 @@ describe('#SolidityDocGenerator', () => {
|
||||
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']);
|
||||
doc = await solDoc.generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, ['NatspecEverything']);
|
||||
expect(doc).to.not.be.undefined();
|
||||
expect(doc.NatspecEverything).to.not.be.undefined();
|
||||
});
|
||||
@@ -159,7 +163,9 @@ describe('#SolidityDocGenerator', () => {
|
||||
});
|
||||
});
|
||||
it('should document a method that returns multiple values', async () => {
|
||||
const doc = await generateSolDocAsync(`${__dirname}/../../test/fixtures/contracts`, ['MultipleReturnValues']);
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user