import * as _ from 'lodash';
import compareVersions = require('compare-versions');
import {constants} from 'ts/utils/constants';
import {utils} from 'ts/utils/utils';
import {
TypeDocNode,
KindString,
ZeroExJsDocSections,
MenuSubsectionsBySection,
TypeDocType,
Type,
DocAgnosticFormat,
DocSection,
TypescriptMethod,
Parameter,
Property,
CustomType,
IndexSignature,
CustomTypeChild,
TypeParameter,
TypeDocTypes,
} from 'ts/types';
const TYPES_MODULE_PATH = '"src/types"';
export const sectionNameToPossibleModulePaths: {[name: string]: string[]} = {
[ZeroExJsDocSections.zeroEx]: ['"src/0x"'],
[ZeroExJsDocSections.exchange]: ['"src/contract_wrappers/exchange_wrapper"'],
[ZeroExJsDocSections.tokenRegistry]: ['"src/contract_wrappers/token_registry_wrapper"'],
[ZeroExJsDocSections.token]: ['"src/contract_wrappers/token_wrapper"'],
[ZeroExJsDocSections.etherToken]: ['"src/contract_wrappers/ether_token_wrapper"'],
[ZeroExJsDocSections.proxy]: [
'"src/contract_wrappers/proxy_wrapper"',
'"src/contract_wrappers/token_transfer_proxy_wrapper"',
],
[ZeroExJsDocSections.types]: [TYPES_MODULE_PATH],
};
export const typeDocUtils = {
isType(entity: TypeDocNode): boolean {
return entity.kindString === KindString.Interface ||
entity.kindString === KindString.Function ||
entity.kindString === KindString['Type alias'] ||
entity.kindString === KindString.Variable ||
entity.kindString === KindString.Enumeration;
},
isMethod(entity: TypeDocNode): boolean {
return entity.kindString === KindString.Method;
},
isConstructor(entity: TypeDocNode): boolean {
return entity.kindString === KindString.Constructor;
},
isProperty(entity: TypeDocNode): boolean {
return entity.kindString === KindString.Property;
},
isPrivateOrProtectedProperty(propertyName: string): boolean {
return _.startsWith(propertyName, '_');
},
isPublicType(typeName: string): boolean {
return _.includes(constants.public0xjsTypes, typeName);
},
getModuleDefinitionBySectionNameIfExists(versionDocObj: TypeDocNode, sectionName: string):
TypeDocNode|undefined {
const possibleModulePathNames = sectionNameToPossibleModulePaths[sectionName];
const modules = versionDocObj.children;
for (const mod of modules) {
if (_.includes(possibleModulePathNames, mod.name)) {
const moduleWithName = mod;
return moduleWithName;
}
}
return undefined;
},
getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection {
const menuSubsectionsBySection = {} as MenuSubsectionsBySection;
if (_.isUndefined(docAgnosticFormat)) {
return menuSubsectionsBySection;
}
const docSections = _.keys(ZeroExJsDocSections);
_.each(docSections, sectionName => {
const docSection = docAgnosticFormat[sectionName];
if (_.isUndefined(docSection)) {
return; // no-op
}
if (sectionName === ZeroExJsDocSections.types) {
const typeNames = _.map(docSection.types, t => t.name);
menuSubsectionsBySection[sectionName] = typeNames;
} else {
const methodNames = _.map(docSection.methods, m => m.name);
menuSubsectionsBySection[sectionName] = methodNames;
}
});
return menuSubsectionsBySection;
},
getFinal0xjsMenu(selectedVersion: string): {[section: string]: string[]} {
const finalMenu = _.cloneDeep(constants.menu0xjs);
finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => {
const versionIntroducedIfExists = constants.menuSubsectionToVersionWhenIntroduced[contractName];
if (!_.isUndefined(versionIntroducedIfExists)) {
const existsInSelectedVersion = compareVersions(selectedVersion,
versionIntroducedIfExists) >= 0;
return existsInSelectedVersion;
} else {
return true;
}
});
return finalMenu;
},
convertToDocAgnosticFormat(typeDocJson: TypeDocNode): DocAgnosticFormat {
const subMenus = _.values(constants.menu0xjs);
const orderedSectionNames = _.flatten(subMenus);
const docAgnosticFormat: DocAgnosticFormat = {};
_.each(orderedSectionNames, sectionName => {
const packageDefinitionIfExists = typeDocUtils.getModuleDefinitionBySectionNameIfExists(
typeDocJson, sectionName,
);
if (_.isUndefined(packageDefinitionIfExists)) {
return; // no-op
}
// Since the `types.ts` file is the only file that does not export a module/class but
// instead has each type export itself, we do not need to go down two levels of nesting
// for it.
let entities;
let packageComment = '';
if (sectionName === ZeroExJsDocSections.types) {
entities = packageDefinitionIfExists.children;
} else {
entities = packageDefinitionIfExists.children[0].children;
const commentObj = packageDefinitionIfExists.children[0].comment;
packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment;
}
const docSection = typeDocUtils._convertEntitiesToDocSection(entities, sectionName);
docSection.comment = packageComment;
docAgnosticFormat[sectionName] = docSection;
});
return docAgnosticFormat;
},
_convertEntitiesToDocSection(entities: TypeDocNode[], sectionName: string) {
const docSection: DocSection = {
comment: '',
constructors: [],
methods: [],
properties: [],
types: [],
};
let isConstructor;
_.each(entities, entity => {
switch (entity.kindString) {
case KindString.Constructor:
isConstructor = true;
const constructor = typeDocUtils._convertMethod(entity, isConstructor, sectionName);
docSection.constructors.push(constructor);
break;
case KindString.Method:
if (entity.flags.isPublic) {
isConstructor = false;
const method = typeDocUtils._convertMethod(entity, isConstructor, sectionName);
docSection.methods.push(method);
}
break;
case KindString.Property:
if (!typeDocUtils.isPrivateOrProtectedProperty(entity.name)) {
const property = typeDocUtils._convertProperty(entity, sectionName);
docSection.properties.push(property);
}
break;
case KindString.Interface:
case KindString.Function:
case KindString.Variable:
case KindString.Enumeration:
case KindString['Type alias']:
if (typeDocUtils.isPublicType(entity.name)) {
const customType = typeDocUtils._convertCustomType(entity, sectionName);
docSection.types.push(customType);
}
break;
default:
throw utils.spawnSwitchErr('kindString', entity.kindString);
}
});
return docSection;
},
_convertCustomType(entity: TypeDocNode, sectionName: string): CustomType {
const typeIfExists = !_.isUndefined(entity.type) ?
typeDocUtils._convertType(entity.type, sectionName) :
undefined;
const isConstructor = false;
const methodIfExists = !_.isUndefined(entity.declaration) ?
typeDocUtils._convertMethod(entity.declaration, isConstructor, sectionName) :
undefined;
const indexSignatureIfExists = !_.isUndefined(entity.indexSignature) ?
typeDocUtils._convertIndexSignature(entity.indexSignature[0], sectionName) :
undefined;
const commentIfExists = !_.isUndefined(entity.comment) && !_.isUndefined(entity.comment.shortText) ?
entity.comment.shortText :
undefined;
const childrenIfExist = !_.isUndefined(entity.children) ?
_.map(entity.children, (child: TypeDocNode) => {
const childTypeIfExists = !_.isUndefined(child.type) ?
typeDocUtils._convertType(child.type, sectionName) :
undefined;
const c: CustomTypeChild = {
name: child.name,
type: childTypeIfExists,
defaultValue: child.defaultValue,
};
return c;
}) :
undefined;
const customType = {
name: entity.name,
kindString: entity.kindString,
type: typeIfExists,
method: methodIfExists,
indexSignature: indexSignatureIfExists,
defaultValue: entity.defaultValue,
comment: commentIfExists,
children: childrenIfExist,
};
return customType;
},
_convertIndexSignature(entity: TypeDocNode, sectionName: string): IndexSignature {
const key = entity.parameters[0];
const indexSignature = {
keyName: key.name,
keyType: typeDocUtils._convertType(key.type, sectionName),
valueName: entity.type.name,
};
return indexSignature;
},
_convertProperty(entity: TypeDocNode, sectionName: string): Property {
const source = entity.sources[0];
const commentIfExists = !_.isUndefined(entity.comment) ? entity.comment.shortText : undefined;
const property = {
name: entity.name,
type: typeDocUtils._convertType(entity.type, sectionName),
source: {
fileName: source.fileName,
line: source.line,
},
comment: commentIfExists,
};
return property;
},
_convertMethod(entity: TypeDocNode, isConstructor: boolean, sectionName: string): TypescriptMethod {
const signature = entity.signatures[0];
const source = entity.sources[0];
const hasComment = !_.isUndefined(signature.comment);
const isStatic = _.isUndefined(entity.flags.isStatic) ? false : entity.flags.isStatic;
const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.';
// HACK: we use the fact that the sectionName is the same as the property name at the top-level
// of the public interface. In the future, we shouldn't use this hack but rather get it from the JSON.
let callPath = (sectionName !== ZeroExJsDocSections.zeroEx) ?
`${topLevelInterface}${sectionName}.` :
topLevelInterface;
callPath = isConstructor ? '' : callPath;
const parameters = _.map(signature.parameters, param => {
return typeDocUtils._convertParameter(param, sectionName);
});
const returnType = typeDocUtils._convertType(signature.type, sectionName);
const typeParameter = _.isUndefined(signature.typeParameter) ?
undefined :
typeDocUtils._convertTypeParameter(signature.typeParameter[0], sectionName);
const method = {
isConstructor,
isStatic,
name: signature.name,
comment: hasComment ? signature.comment.shortText : undefined,
returnComment: hasComment && signature.comment.returns ? signature.comment.returns : undefined,
source: {
fileName: source.fileName,
line: source.line,
},
callPath,
parameters,
returnType,
typeParameter,
};
return method;
},
_convertTypeParameter(entity: TypeDocNode, sectionName: string): TypeParameter {
const type = typeDocUtils._convertType(entity.type, sectionName);
const parameter = {
name: entity.name,
type,
};
return parameter;
},
_convertParameter(entity: TypeDocNode, sectionName: string): Parameter {
let comment = '<No comment>';
if (entity.comment && entity.comment.shortText) {
comment = entity.comment.shortText;
} else if (entity.comment && entity.comment.text) {
comment = entity.comment.text;
}
const isOptional = !_.isUndefined(entity.flags.isOptional) ?
entity.flags.isOptional :
false;
const type = typeDocUtils._convertType(entity.type, sectionName);
const parameter = {
name: entity.name,
comment,
isOptional,
type,
};
return parameter;
},
_convertType(entity: TypeDocType, sectionName: string): Type {
const typeArguments = _.map(entity.typeArguments, typeArgument => {
return typeDocUtils._convertType(typeArgument, sectionName);
});
const types = _.map(entity.types, t => {
return typeDocUtils._convertType(t, sectionName);
});
const isConstructor = false;
const methodIfExists = !_.isUndefined(entity.declaration) ?
typeDocUtils._convertMethod(entity.declaration, isConstructor, sectionName) :
undefined;
const elementTypeIfExists = !_.isUndefined(entity.elementType) ?
{
name: entity.elementType.name,
typeDocType: entity.elementType.type,
} :
undefined;
const type = {
name: entity.name,
value: entity.value,
typeDocType: entity.type,
typeArguments,
elementType: elementTypeIfExists,
types,
method: methodIfExists,
};
return type;
},
};