Move Documentation to the @0xproject/react-docs package

This commit is contained in:
Fabio Berger
2018-03-06 16:31:55 +01:00
parent f014a97e9a
commit 0b1ba9f997
59 changed files with 653 additions and 488 deletions

View File

@@ -0,0 +1,56 @@
import { Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
const styles: Styles = {
badge: {
width: 50,
fontSize: 11,
height: 10,
borderRadius: 5,
lineHeight: 0.9,
fontFamily: 'Roboto Mono',
marginLeft: 3,
marginRight: 3,
},
};
export interface BadgeProps {
title: string;
backgroundColor: string;
}
export interface BadgeState {
isHovering: boolean;
}
export class Badge extends React.Component<BadgeProps, BadgeState> {
constructor(props: BadgeProps) {
super(props);
this.state = {
isHovering: false,
};
}
public render() {
const badgeStyle = {
...styles.badge,
backgroundColor: this.props.backgroundColor,
opacity: this.state.isHovering ? 0.7 : 1,
};
return (
<div
className="p1 center"
style={badgeStyle}
onMouseOver={this._setHoverState.bind(this, true)}
onMouseOut={this._setHoverState.bind(this, false)}
>
{this.props.title}
</div>
);
}
private _setHoverState(isHovering: boolean) {
this.setState({
isHovering,
});
}
}

View File

@@ -0,0 +1,23 @@
import { MarkdownCodeBlock } from '@0xproject/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
import * as ReactMarkdown from 'react-markdown';
export interface CommentProps {
comment: string;
className?: string;
}
const defaultProps = {
className: '',
};
export const Comment: React.SFC<CommentProps> = (props: CommentProps) => {
return (
<div className={`${props.className} comment`}>
<ReactMarkdown source={props.comment} renderers={{ code: MarkdownCodeBlock }} />
</div>
);
};
Comment.defaultProps = defaultProps;

View File

@@ -0,0 +1,33 @@
import * as _ from 'lodash';
import * as React from 'react';
import { CustomType } from '../types';
import { utils } from '../utils/utils';
const STRING_ENUM_CODE_PREFIX = ' strEnum(';
export interface CustomEnumProps {
type: CustomType;
}
// This component renders custom string enums that was a work-around for versions of
// TypeScript <2.4.0 that did not support them natively. We keep it around to support
// older versions of 0x.js <0.9.0
export function CustomEnum(props: CustomEnumProps) {
const type = props.type;
if (!_.startsWith(type.defaultValue, STRING_ENUM_CODE_PREFIX)) {
utils.consoleLog('We do not yet support `Variable` types that are not strEnums');
return null;
}
// Remove the prefix and postfix, leaving only the strEnum values without quotes.
const enumValues = type.defaultValue.slice(10, -3).replace(/'/g, '');
return (
<span>
{`{`}
{'\t'}
{enumValues}
<br />
{`}`}
</span>
);
}

View File

@@ -0,0 +1,120 @@
import { MenuSubsectionsBySection } from '@0xproject/react-shared';
import compareVersions = require('compare-versions');
import * as _ from 'lodash';
import {
ContractsByVersionByNetworkId,
DocAgnosticFormat,
DocsInfoConfig,
DocsMenu,
DoxityDocObj,
SectionsMap,
SupportedDocJson,
TypeDocNode,
} from '../types';
import { doxityUtils } from '../utils/doxity_utils';
import { typeDocUtils } from '../utils/typedoc_utils';
export class DocsInfo {
public id: string;
public type: SupportedDocJson;
public displayName: string;
public packageUrl: string;
public menu: DocsMenu;
public sections: SectionsMap;
public sectionNameToMarkdown: { [sectionName: string]: string };
public contractsByVersionByNetworkId?: ContractsByVersionByNetworkId;
private _docsInfo: DocsInfoConfig;
constructor(config: DocsInfoConfig) {
this.id = config.id;
this.type = config.type;
this.displayName = config.displayName;
this.packageUrl = config.packageUrl;
this.sections = config.sections;
this.sectionNameToMarkdown = config.sectionNameToMarkdown;
this.contractsByVersionByNetworkId = config.contractsByVersionByNetworkId;
this._docsInfo = config;
}
public isPublicType(typeName: string): boolean {
if (_.isUndefined(this._docsInfo.publicTypes)) {
return false;
}
const isPublic = _.includes(this._docsInfo.publicTypes, typeName);
return isPublic;
}
public getModulePathsIfExists(sectionName: string): string[] {
const modulePathsIfExists = this._docsInfo.sectionNameToModulePath[sectionName];
return modulePathsIfExists;
}
public getMenu(selectedVersion?: string): { [section: string]: string[] } {
if (_.isUndefined(selectedVersion) || _.isUndefined(this._docsInfo.menuSubsectionToVersionWhenIntroduced)) {
return this._docsInfo.menu;
}
const finalMenu = _.cloneDeep(this._docsInfo.menu);
if (_.isUndefined(finalMenu.contracts)) {
return finalMenu;
}
// TODO: refactor to include more sections then simply the `contracts` section
finalMenu.contracts = _.filter(finalMenu.contracts, (contractName: string) => {
const versionIntroducedIfExists = this._docsInfo.menuSubsectionToVersionWhenIntroduced[contractName];
if (!_.isUndefined(versionIntroducedIfExists)) {
const existsInSelectedVersion = compareVersions(selectedVersion, versionIntroducedIfExists) >= 0;
return existsInSelectedVersion;
} else {
return true;
}
});
return finalMenu;
}
public getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection {
const menuSubsectionsBySection = {} as MenuSubsectionsBySection;
if (_.isUndefined(docAgnosticFormat)) {
return menuSubsectionsBySection;
}
const docSections = _.keys(this.sections);
_.each(docSections, sectionName => {
const docSection = docAgnosticFormat[sectionName];
if (_.isUndefined(docSection)) {
return; // no-op
}
if (!_.isUndefined(this.sections.types) && sectionName === this.sections.types) {
const sortedTypesNames = _.sortBy(docSection.types, 'name');
const typeNames = _.map(sortedTypesNames, t => t.name);
menuSubsectionsBySection[sectionName] = typeNames;
} else {
let eventNames: string[] = [];
if (!_.isUndefined(docSection.events)) {
const sortedEventNames = _.sortBy(docSection.events, 'name');
eventNames = _.map(sortedEventNames, m => m.name);
}
const sortedMethodNames = _.sortBy(docSection.methods, 'name');
const methodNames = _.map(sortedMethodNames, m => m.name);
menuSubsectionsBySection[sectionName] = [...methodNames, ...eventNames];
}
});
return menuSubsectionsBySection;
}
public getTypeDefinitionsByName(docAgnosticFormat: DocAgnosticFormat) {
if (_.isUndefined(this.sections.types)) {
return {};
}
const typeDocSection = docAgnosticFormat[this.sections.types];
const typeDefinitionByName = _.keyBy(typeDocSection.types, 'name');
return typeDefinitionByName;
}
public isVisibleConstructor(sectionName: string): boolean {
return _.includes(this._docsInfo.visibleConstructors, sectionName);
}
public convertToDocAgnosticFormat(docObj: DoxityDocObj | TypeDocNode): DocAgnosticFormat {
if (this.type === SupportedDocJson.Doxity) {
return doxityUtils.convertToDocAgnosticFormat(docObj as DoxityDocObj);
} else {
return typeDocUtils.convertToDocAgnosticFormat(docObj as TypeDocNode, this);
}
}
}

View File

@@ -0,0 +1,337 @@
import {
colors,
constants as sharedConstants,
EtherscanLinkSuffixes,
MarkdownSection,
MenuSubsectionsBySection,
NestedSidebarMenu,
Networks,
SectionHeader,
Styles,
utils as sharedUtils,
} from '@0xproject/react-shared';
import * as _ from 'lodash';
import CircularProgress from 'material-ui/CircularProgress';
import * as React from 'react';
import { scroller } from 'react-scroll';
import {
AddressByContractName,
DocAgnosticFormat,
DoxityDocObj,
Event,
Property,
SolidityMethod,
SupportedDocJson,
TypeDefinitionByName,
TypescriptMethod,
} from '../types';
import { utils } from '../utils/utils';
import { Badge } from './badge';
import { Comment } from './comment';
import { DocsInfo } from './docs_info';
import { EventDefinition } from './event_definition';
import { MethodBlock } from './method_block';
import { SourceLink } from './source_link';
import { Type } from './type';
import { TypeDefinition } from './type_definition';
const TOP_BAR_HEIGHT = 60;
const networkNameToColor: { [network: string]: string } = {
[Networks.Kovan]: colors.purple,
[Networks.Ropsten]: colors.red,
[Networks.Mainnet]: colors.turquois,
[Networks.Rinkeby]: colors.darkYellow,
};
export interface DocumentationProps {
location: Location;
docsVersion: string;
availableDocVersions: string[];
docsInfo: DocsInfo;
docAgnosticFormat?: DocAgnosticFormat;
menuSubsectionsBySection: MenuSubsectionsBySection;
sourceUrl: string;
}
export interface DocumentationState {}
const styles: Styles = {
mainContainers: {
position: 'absolute',
top: 1,
left: 0,
bottom: 0,
right: 0,
overflowZ: 'hidden',
overflowY: 'scroll',
minHeight: `calc(100vh - ${TOP_BAR_HEIGHT}px)`,
WebkitOverflowScrolling: 'touch',
},
menuContainer: {
borderColor: colors.grey300,
maxWidth: 330,
marginLeft: 20,
},
};
export class Documentation extends React.Component<DocumentationProps, DocumentationState> {
public componentDidUpdate(prevProps: DocumentationProps, prevState: DocumentationState) {
if (!_.isEqual(prevProps.docAgnosticFormat, this.props.docAgnosticFormat)) {
const hash = this.props.location.hash.slice(1);
sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID);
}
}
public render() {
return (
<div>
{_.isUndefined(this.props.docAgnosticFormat) ? (
this._renderLoading()
) : (
<div style={{ width: '100%', height: '100%', backgroundColor: colors.gray40 }}>
<div
className="mx-auto max-width-4 flex"
style={{ color: colors.grey800, height: `calc(100vh - ${TOP_BAR_HEIGHT}px)` }}
>
<div
className="relative sm-hide xs-hide"
style={{ width: '36%', height: `calc(100vh - ${TOP_BAR_HEIGHT}px)` }}
>
<div
className="border-right absolute"
style={{
...styles.menuContainer,
...styles.mainContainers,
height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`,
}}
>
<NestedSidebarMenu
selectedVersion={this.props.docsVersion}
versions={this.props.availableDocVersions}
title={this.props.docsInfo.displayName}
topLevelMenu={this.props.docsInfo.getMenu(this.props.docsVersion)}
menuSubsectionsBySection={this.props.menuSubsectionsBySection}
/>
</div>
</div>
<div
className="relative col lg-col-9 md-col-9 sm-col-12 col-12"
style={{ backgroundColor: colors.white }}
>
<div
id={sharedConstants.SCROLL_CONTAINER_ID}
style={styles.mainContainers}
className="absolute px1"
>
<div id={sharedConstants.SCROLL_TOP_ID} />
{this._renderDocumentation()}
</div>
</div>
</div>
</div>
)}
</div>
);
}
private _renderLoading() {
return (
<div className="col col-12" style={styles.mainContainers}>
<div
className="relative sm-px2 sm-pt2 sm-m1"
style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }}
>
<div className="center pb2">
<CircularProgress size={40} thickness={5} />
</div>
<div className="center pt2" style={{ paddingBottom: 11 }}>
Loading documentation...
</div>
</div>
</div>
);
}
private _renderDocumentation(): React.ReactNode {
const subMenus = _.values(this.props.docsInfo.getMenu());
const orderedSectionNames = _.flatten(subMenus);
const typeDefinitionByName = this.props.docsInfo.getTypeDefinitionsByName(this.props.docAgnosticFormat);
const renderedSections = _.map(orderedSectionNames, this._renderSection.bind(this, typeDefinitionByName));
return renderedSections;
}
private _renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode {
const markdownFileIfExists = this.props.docsInfo.sectionNameToMarkdown[sectionName];
if (!_.isUndefined(markdownFileIfExists)) {
return (
<MarkdownSection
key={`markdown-section-${sectionName}`}
sectionName={sectionName}
markdownContent={markdownFileIfExists}
/>
);
}
const docSection = this.props.docAgnosticFormat[sectionName];
if (_.isUndefined(docSection)) {
return null;
}
const sortedTypes = _.sortBy(docSection.types, 'name');
const typeDefs = _.map(sortedTypes, customType => {
return (
<TypeDefinition
sectionName={sectionName}
key={`type-${customType.name}`}
customType={customType}
docsInfo={this.props.docsInfo}
/>
);
});
const sortedProperties = _.sortBy(docSection.properties, 'name');
const propertyDefs = _.map(sortedProperties, this._renderProperty.bind(this, sectionName));
const sortedMethods = _.sortBy(docSection.methods, 'name');
const methodDefs = _.map(sortedMethods, method => {
const isConstructor = false;
return this._renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName);
});
const sortedEvents = _.sortBy(docSection.events, 'name');
const eventDefs = _.map(sortedEvents, (event: Event, i: number) => {
return (
<EventDefinition
key={`event-${event.name}-${i}`}
event={event}
sectionName={sectionName}
docsInfo={this.props.docsInfo}
/>
);
});
return (
<div key={`section-${sectionName}`} className="py2 pr3 md-pl2 sm-pl3">
<div className="flex pb2">
<div style={{ marginRight: 7 }}>
<SectionHeader sectionName={sectionName} />
</div>
{this._renderNetworkBadgesIfExists(sectionName)}
</div>
{docSection.comment && <Comment comment={docSection.comment} />}
{docSection.constructors.length > 0 &&
this.props.docsInfo.isVisibleConstructor(sectionName) && (
<div>
<h2 className="thin">Constructor</h2>
{this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)}
</div>
)}
{docSection.properties.length > 0 && (
<div>
<h2 className="thin">Properties</h2>
<div>{propertyDefs}</div>
</div>
)}
{docSection.methods.length > 0 && (
<div>
<h2 className="thin">Methods</h2>
<div>{methodDefs}</div>
</div>
)}
{!_.isUndefined(docSection.events) &&
docSection.events.length > 0 && (
<div>
<h2 className="thin">Events</h2>
<div>{eventDefs}</div>
</div>
)}
{!_.isUndefined(typeDefs) &&
typeDefs.length > 0 && (
<div>
<div>{typeDefs}</div>
</div>
)}
</div>
);
}
private _renderNetworkBadgesIfExists(sectionName: string) {
if (this.props.docsInfo.type !== SupportedDocJson.Doxity) {
return null;
}
const networkToAddressByContractName = this.props.docsInfo.contractsByVersionByNetworkId[
this.props.docsVersion
];
const badges = _.map(
networkToAddressByContractName,
(addressByContractName: AddressByContractName, networkName: string) => {
const contractAddress = addressByContractName[sectionName];
if (_.isUndefined(contractAddress)) {
return null;
}
const linkIfExists = sharedUtils.getEtherScanLinkIfExists(
contractAddress,
sharedConstants.NETWORK_ID_BY_NAME[networkName],
EtherscanLinkSuffixes.Address,
);
return (
<a
key={`badge-${networkName}-${sectionName}`}
href={linkIfExists}
target="_blank"
style={{ color: colors.white, textDecoration: 'none' }}
>
<Badge title={networkName} backgroundColor={networkNameToColor[networkName]} />
</a>
);
},
);
return badges;
}
private _renderConstructors(
constructors: SolidityMethod[] | TypescriptMethod[],
sectionName: string,
typeDefinitionByName: TypeDefinitionByName,
): React.ReactNode {
const constructorDefs = _.map(constructors, constructor => {
return this._renderMethodBlocks(constructor, sectionName, constructor.isConstructor, typeDefinitionByName);
});
return <div>{constructorDefs}</div>;
}
private _renderProperty(sectionName: string, property: Property): React.ReactNode {
return (
<div key={`property-${property.name}-${property.type.name}`} className="pb3">
<code className="hljs">
{property.name}:
<Type type={property.type} sectionName={sectionName} docsInfo={this.props.docsInfo} />
</code>
{property.source && (
<SourceLink
version={this.props.docsVersion}
source={property.source}
sourceUrl={this.props.sourceUrl}
/>
)}
{property.comment && <Comment comment={property.comment} className="py2" />}
</div>
);
}
private _renderMethodBlocks(
method: SolidityMethod | TypescriptMethod,
sectionName: string,
isConstructor: boolean,
typeDefinitionByName: TypeDefinitionByName,
): React.ReactNode {
return (
<MethodBlock
key={`method-${method.name}-${sectionName}`}
sectionName={sectionName}
method={method}
typeDefinitionByName={typeDefinitionByName}
libraryVersion={this.props.docsVersion}
docsInfo={this.props.docsInfo}
sourceUrl={this.props.sourceUrl}
/>
);
}
}

View File

@@ -0,0 +1,23 @@
import * as _ from 'lodash';
import * as React from 'react';
import { EnumValue } from '../types';
export interface EnumProps {
values: EnumValue[];
}
export function Enum(props: EnumProps) {
const values = _.map(props.values, (value, i) => {
const defaultValueIfAny = !_.isUndefined(value.defaultValue) ? ` = ${value.defaultValue}` : '';
return `\n\t${value.name}${defaultValueIfAny},`;
});
return (
<span>
{`{`}
{values}
<br />
{`}`}
</span>
);
}

View File

@@ -0,0 +1,84 @@
import { AnchorTitle, colors, HeaderSizes } from '@0xproject/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
import { Event, EventArg } from '../types';
import { DocsInfo } from './docs_info';
import { Type } from './type';
export interface EventDefinitionProps {
event: Event;
sectionName: string;
docsInfo: DocsInfo;
}
export interface EventDefinitionState {
shouldShowAnchor: boolean;
}
export class EventDefinition extends React.Component<EventDefinitionProps, EventDefinitionState> {
constructor(props: EventDefinitionProps) {
super(props);
this.state = {
shouldShowAnchor: false,
};
}
public render() {
const event = this.props.event;
const id = `${this.props.sectionName}-${event.name}`;
return (
<div
id={id}
className="pb2"
style={{ overflow: 'hidden', width: '100%' }}
onMouseOver={this._setAnchorVisibility.bind(this, true)}
onMouseOut={this._setAnchorVisibility.bind(this, false)}
>
<AnchorTitle
headerSize={HeaderSizes.H3}
title={`Event ${event.name}`}
id={id}
shouldShowAnchor={this.state.shouldShowAnchor}
/>
<div style={{ fontSize: 16 }}>
<pre>
<code className="hljs">{this._renderEventCode()}</code>
</pre>
</div>
</div>
);
}
private _renderEventCode() {
const indexed = <span style={{ color: colors.green }}> indexed</span>;
const eventArgs = _.map(this.props.event.eventArgs, (eventArg: EventArg) => {
const type = (
<Type type={eventArg.type} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} />
);
return (
<span key={`eventArg-${eventArg.name}`}>
{eventArg.name}
{eventArg.isIndexed ? indexed : ''}: {type},
</span>
);
});
const argList = _.reduce(eventArgs, (prev: React.ReactNode, curr: React.ReactNode) => {
return [prev, '\n\t', curr];
});
return (
<span>
{`{`}
<br />
{'\t'}
{argList}
<br />
{`}`}
</span>
);
}
private _setAnchorVisibility(shouldShowAnchor: boolean) {
this.setState({
shouldShowAnchor,
});
}
}

View File

@@ -0,0 +1,63 @@
import * as _ from 'lodash';
import * as React from 'react';
import { CustomType, TypeDocTypes } from '../types';
import { DocsInfo } from './docs_info';
import { MethodSignature } from './method_signature';
import { Type } from './type';
export interface InterfaceProps {
type: CustomType;
sectionName: string;
docsInfo: DocsInfo;
}
export function Interface(props: InterfaceProps) {
const type = props.type;
const properties = _.map(type.children, property => {
return (
<span key={`property-${property.name}-${property.type}-${type.name}`}>
{property.name}:{' '}
{property.type.typeDocType !== TypeDocTypes.Reflection ? (
<Type type={property.type} sectionName={props.sectionName} docsInfo={props.docsInfo} />
) : (
<MethodSignature
method={property.type.method}
sectionName={props.sectionName}
shouldHideMethodName={true}
shouldUseArrowSyntax={true}
docsInfo={props.docsInfo}
/>
)},
</span>
);
});
const hasIndexSignature = !_.isUndefined(type.indexSignature);
if (hasIndexSignature) {
const is = type.indexSignature;
const param = (
<span key={`indexSigParams-${is.keyName}-${is.keyType}-${type.name}`}>
{is.keyName}: <Type type={is.keyType} sectionName={props.sectionName} docsInfo={props.docsInfo} />
</span>
);
properties.push(
<span key={`indexSignature-${type.name}-${is.keyType.name}`}>
[{param}]: {is.valueName},
</span>,
);
}
const propertyList = _.reduce(properties, (prev: React.ReactNode, curr: React.ReactNode) => {
return [prev, '\n\t', curr];
});
return (
<span>
{`{`}
<br />
{'\t'}
{propertyList}
<br />
{`}`}
</span>
);
}

View File

@@ -0,0 +1,149 @@
import { AnchorTitle, colors, HeaderSizes, Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from '../types';
import { typeDocUtils } from '../utils/typedoc_utils';
import { Comment } from './comment';
import { DocsInfo } from './docs_info';
import { MethodSignature } from './method_signature';
import { SourceLink } from './source_link';
export interface MethodBlockProps {
method: SolidityMethod | TypescriptMethod;
sectionName: string;
libraryVersion: string;
typeDefinitionByName: TypeDefinitionByName;
docsInfo: DocsInfo;
sourceUrl: string;
}
export interface MethodBlockState {
shouldShowAnchor: boolean;
}
const styles: Styles = {
chip: {
fontSize: 13,
backgroundColor: colors.lightBlueA700,
color: colors.white,
height: 11,
borderRadius: 14,
lineHeight: 0.9,
},
};
export class MethodBlock extends React.Component<MethodBlockProps, MethodBlockState> {
constructor(props: MethodBlockProps) {
super(props);
this.state = {
shouldShowAnchor: false,
};
}
public render() {
const method = this.props.method;
if (typeDocUtils.isPrivateOrProtectedProperty(method.name)) {
return null;
}
return (
<div
id={`${this.props.sectionName}-${method.name}`}
style={{ overflow: 'hidden', width: '100%' }}
className="pb4"
onMouseOver={this._setAnchorVisibility.bind(this, true)}
onMouseOut={this._setAnchorVisibility.bind(this, false)}
>
{!method.isConstructor && (
<div className="flex pb2 pt2">
{(method as TypescriptMethod).isStatic && this._renderChip('Static')}
{(method as SolidityMethod).isConstant && this._renderChip('Constant')}
{(method as SolidityMethod).isPayable && this._renderChip('Payable')}
<div style={{ lineHeight: 1.3 }}>
<AnchorTitle
headerSize={HeaderSizes.H3}
title={method.name}
id={`${this.props.sectionName}-${method.name}`}
shouldShowAnchor={this.state.shouldShowAnchor}
/>
</div>
</div>
)}
<code className="hljs">
<MethodSignature
method={method}
sectionName={this.props.sectionName}
typeDefinitionByName={this.props.typeDefinitionByName}
docsInfo={this.props.docsInfo}
/>
</code>
{(method as TypescriptMethod).source && (
<SourceLink
version={this.props.libraryVersion}
source={(method as TypescriptMethod).source}
sourceUrl={this.props.sourceUrl}
/>
)}
{method.comment && <Comment comment={method.comment} className="py2" />}
{method.parameters &&
!_.isEmpty(method.parameters) && (
<div>
<h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}>
ARGUMENTS
</h4>
{this._renderParameterDescriptions(method.parameters)}
</div>
)}
{method.returnComment && (
<div className="pt1 comment">
<h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}>
RETURNS
</h4>
<Comment comment={method.returnComment} />
</div>
)}
</div>
);
}
private _renderChip(text: string) {
return (
<div className="p1 mr1" style={styles.chip}>
{text}
</div>
);
}
private _renderParameterDescriptions(parameters: Parameter[]) {
const descriptions = _.map(parameters, parameter => {
const isOptional = parameter.isOptional;
return (
<div
key={`param-description-${parameter.name}`}
className="flex pb1 mb2"
style={{ borderBottom: '1px solid #f0f4f7' }}
>
<div className="pl2 col lg-col-4 md-col-4 sm-col-12 col-12">
<div
className="bold"
style={{ overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}
>
{parameter.name}
</div>
<div className="pt1" style={{ color: colors.grey, fontSize: 14 }}>
{isOptional && 'optional'}
</div>
</div>
<div className="col lg-col-8 md-col-8 sm-col-12 col-12" style={{ paddingLeft: 5 }}>
{parameter.comment && <Comment comment={parameter.comment} />}
</div>
</div>
);
});
return descriptions;
}
private _setAnchorVisibility(shouldShowAnchor: boolean) {
this.setState({
shouldShowAnchor,
});
}
}

View File

@@ -0,0 +1,128 @@
import * as _ from 'lodash';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from '../types';
import { constants } from '../utils/constants';
import { DocsInfo } from './docs_info';
import { Type } from './type';
export interface MethodSignatureProps {
method: TypescriptMethod | SolidityMethod;
sectionName: string;
shouldHideMethodName?: boolean;
shouldUseArrowSyntax?: boolean;
typeDefinitionByName?: TypeDefinitionByName;
docsInfo: DocsInfo;
}
const defaultProps = {
shouldHideMethodName: false,
shouldUseArrowSyntax: false,
};
export const MethodSignature: React.SFC<MethodSignatureProps> = (props: MethodSignatureProps) => {
const sectionName = constants.TYPES_SECTION_NAME;
const parameters = renderParameters(props.method, props.docsInfo, sectionName, props.typeDefinitionByName);
const paramStringArray: any[] = [];
// HACK: For now we don't put params on newlines if there are less then 2 of them.
// Ideally we would check the character length of the resulting method signature and
// if it exceeds the available space, put params on their own lines.
const hasMoreThenTwoParams = parameters.length > 2;
_.each(parameters, (param: React.ReactNode, i: number) => {
const finalParam = hasMoreThenTwoParams ? (
<span className="pl2" key={`param-${i}`}>
{param}
</span>
) : (
param
);
paramStringArray.push(finalParam);
const comma = hasMoreThenTwoParams ? (
<span key={`param-comma-${i}`}>
, <br />
</span>
) : (
', '
);
paramStringArray.push(comma);
});
if (!hasMoreThenTwoParams) {
paramStringArray.pop();
}
const methodName = props.shouldHideMethodName ? '' : props.method.name;
const typeParameterIfExists = _.isUndefined((props.method as TypescriptMethod).typeParameter)
? undefined
: renderTypeParameter(props.method, props.docsInfo, sectionName, props.typeDefinitionByName);
return (
<span style={{ fontSize: 15 }}>
{props.method.callPath}
{methodName}
{typeParameterIfExists}({hasMoreThenTwoParams && <br />}
{paramStringArray})
{props.method.returnType && (
<span>
{props.shouldUseArrowSyntax ? ' => ' : ': '}{' '}
<Type
type={props.method.returnType}
sectionName={sectionName}
typeDefinitionByName={props.typeDefinitionByName}
docsInfo={props.docsInfo}
/>
</span>
)}
</span>
);
};
MethodSignature.defaultProps = defaultProps;
function renderParameters(
method: TypescriptMethod | SolidityMethod,
docsInfo: DocsInfo,
sectionName: string,
typeDefinitionByName?: TypeDefinitionByName,
) {
const parameters = method.parameters;
const params = _.map(parameters, (p: Parameter) => {
const isOptional = p.isOptional;
const type = (
<Type
type={p.type}
sectionName={sectionName}
typeDefinitionByName={typeDefinitionByName}
docsInfo={docsInfo}
/>
);
return (
<span key={`param-${p.type}-${p.name}`}>
{p.name}
{isOptional && '?'}: {type}
</span>
);
});
return params;
}
function renderTypeParameter(
method: TypescriptMethod,
docsInfo: DocsInfo,
sectionName: string,
typeDefinitionByName?: TypeDefinitionByName,
) {
const typeParameter = method.typeParameter;
const typeParam = (
<span>
{`<${typeParameter.name} extends `}
<Type
type={typeParameter.type}
sectionName={sectionName}
typeDefinitionByName={typeDefinitionByName}
docsInfo={docsInfo}
/>
{`>`}
</span>
);
return typeParam;
}

View File

@@ -0,0 +1,23 @@
import { colors } from '@0xproject/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
import { Source } from '../types';
export interface SourceLinkProps {
source: Source;
sourceUrl: string;
version: string;
}
export function SourceLink(props: SourceLinkProps) {
const src = props.source;
const sourceCodeUrl = `${props.sourceUrl}/${src.fileName}#L${src.line}`;
return (
<div className="pt2" style={{ fontSize: 14 }}>
<a href={sourceCodeUrl} target="_blank" className="underline" style={{ color: colors.grey }}>
Source
</a>
</div>
);
}

View File

@@ -0,0 +1,231 @@
import { colors, constants as sharedConstants, utils as sharedUtils } from '@0xproject/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
import { Link as ScrollLink } from 'react-scroll';
import * as ReactTooltip from 'react-tooltip';
import { Type as TypeDef, TypeDefinitionByName, TypeDocTypes } from '../types';
import { constants } from '../utils/constants';
import { utils } from '../utils/utils';
import { DocsInfo } from './docs_info';
import { TypeDefinition } from './type_definition';
// Some types reference other libraries. For these types, we want to link the user to the relevant documentation.
const typeToUrl: { [typeName: string]: string } = {
Web3: constants.URL_WEB3_DOCS,
Provider: constants.URL_WEB3_PROVIDER_DOCS,
BigNumber: constants.URL_BIGNUMBERJS_GITHUB,
DecodedLogEntryEvent: constants.URL_WEB3_DECODED_LOG_ENTRY_EVENT,
LogEntryEvent: constants.URL_WEB3_LOG_ENTRY_EVENT,
};
const typePrefix: { [typeName: string]: string } = {
Provider: 'Web3',
DecodedLogEntryEvent: 'Web3',
LogEntryEvent: 'Web3',
};
const typeToSection: { [typeName: string]: string } = {
ExchangeWrapper: 'exchange',
TokenWrapper: 'token',
TokenRegistryWrapper: 'tokenRegistry',
EtherTokenWrapper: 'etherToken',
ProxyWrapper: 'proxy',
TokenTransferProxyWrapper: 'proxy',
OrderStateWatcher: 'orderWatcher',
};
export interface TypeProps {
type: TypeDef;
docsInfo: DocsInfo;
sectionName: string;
typeDefinitionByName?: TypeDefinitionByName;
}
// The return type needs to be `any` here so that we can recursively define <Type /> components within
// <Type /> components (e.g when rendering the union type).
export function Type(props: TypeProps): any {
const type = props.type;
const isReference = type.typeDocType === TypeDocTypes.Reference;
const isArray = type.typeDocType === TypeDocTypes.Array;
let typeNameColor = 'inherit';
let typeName: string | React.ReactNode;
let typeArgs: React.ReactNode[] = [];
switch (type.typeDocType) {
case TypeDocTypes.Intrinsic:
case TypeDocTypes.Unknown:
typeName = type.name;
typeNameColor = colors.orange;
break;
case TypeDocTypes.Reference:
typeName = type.name;
typeArgs = _.map(type.typeArguments, (arg: TypeDef) => {
if (arg.typeDocType === TypeDocTypes.Array) {
const key = `type-${arg.elementType.name}-${arg.elementType.typeDocType}`;
return (
<span>
<Type
key={key}
type={arg.elementType}
sectionName={props.sectionName}
typeDefinitionByName={props.typeDefinitionByName}
docsInfo={props.docsInfo}
/>[]
</span>
);
} else {
const subType = (
<Type
key={`type-${arg.name}-${arg.value}-${arg.typeDocType}`}
type={arg}
sectionName={props.sectionName}
typeDefinitionByName={props.typeDefinitionByName}
docsInfo={props.docsInfo}
/>
);
return subType;
}
});
break;
case TypeDocTypes.StringLiteral:
typeName = `'${type.value}'`;
typeNameColor = colors.green;
break;
case TypeDocTypes.Array:
typeName = type.elementType.name;
break;
case TypeDocTypes.Union:
const unionTypes = _.map(type.types, t => {
return (
<Type
key={`type-${t.name}-${t.value}-${t.typeDocType}`}
type={t}
sectionName={props.sectionName}
typeDefinitionByName={props.typeDefinitionByName}
docsInfo={props.docsInfo}
/>
);
});
typeName = _.reduce(unionTypes, (prev: React.ReactNode, curr: React.ReactNode) => {
return [prev, '|', curr];
});
break;
case TypeDocTypes.TypeParameter:
typeName = type.name;
break;
case TypeDocTypes.Intersection:
const intersectionsTypes = _.map(type.types, t => {
return (
<Type
key={`type-${t.name}-${t.value}-${t.typeDocType}`}
type={t}
sectionName={props.sectionName}
typeDefinitionByName={props.typeDefinitionByName}
docsInfo={props.docsInfo}
/>
);
});
typeName = _.reduce(intersectionsTypes, (prev: React.ReactNode, curr: React.ReactNode) => {
return [prev, '&', curr];
});
break;
default:
throw utils.spawnSwitchErr('type.typeDocType', type.typeDocType);
}
// HACK: Normalize BigNumber to simply BigNumber. For some reason the type
// name is unpredictably one or the other.
if (typeName === 'BigNumber') {
typeName = 'BigNumber';
}
const commaSeparatedTypeArgs = _.reduce(typeArgs, (prev: React.ReactNode, curr: React.ReactNode) => {
return [prev, ', ', curr];
});
const typeNameUrlIfExists = typeToUrl[typeName as string];
const typePrefixIfExists = typePrefix[typeName as string];
const sectionNameIfExists = typeToSection[typeName as string];
if (!_.isUndefined(typeNameUrlIfExists)) {
typeName = (
<a
href={typeNameUrlIfExists}
target="_blank"
className="text-decoration-none"
style={{ color: colors.lightBlueA700 }}
>
{!_.isUndefined(typePrefixIfExists) ? `${typePrefixIfExists}.` : ''}
{typeName}
</a>
);
} else if (
(isReference || isArray) &&
(props.docsInfo.isPublicType(typeName as string) || !_.isUndefined(sectionNameIfExists))
) {
const id = Math.random().toString();
const typeDefinitionAnchorId = _.isUndefined(sectionNameIfExists)
? `${props.sectionName}-${typeName}`
: sectionNameIfExists;
let typeDefinition;
if (props.typeDefinitionByName) {
typeDefinition = props.typeDefinitionByName[typeName as string];
}
typeName = (
<ScrollLink
to={typeDefinitionAnchorId}
offset={0}
duration={sharedConstants.DOCS_SCROLL_DURATION_MS}
containerId={sharedConstants.DOCS_CONTAINER_ID}
>
{_.isUndefined(typeDefinition) || sharedUtils.isUserOnMobile() ? (
<span
onClick={sharedUtils.setUrlHash.bind(null, typeDefinitionAnchorId)}
style={{ color: colors.lightBlueA700, cursor: 'pointer' }}
>
{typeName}
</span>
) : (
<span
data-tip={true}
data-for={id}
onClick={sharedUtils.setUrlHash.bind(null, typeDefinitionAnchorId)}
style={{
color: colors.lightBlueA700,
cursor: 'pointer',
display: 'inline-block',
}}
>
{typeName}
<ReactTooltip type="light" effect="solid" id={id} className="typeTooltip">
<TypeDefinition
sectionName={props.sectionName}
customType={typeDefinition}
shouldAddId={false}
docsInfo={props.docsInfo}
/>
</ReactTooltip>
</span>
)}
</ScrollLink>
);
}
return (
<span>
<span style={{ color: typeNameColor }}>{typeName}</span>
{isArray && '[]'}
{!_.isEmpty(typeArgs) && (
<span>
{'<'}
{commaSeparatedTypeArgs}
{'>'}
</span>
)}
</span>
);
}

View File

@@ -0,0 +1,128 @@
import { AnchorTitle, colors, HeaderSizes } from '@0xproject/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
import { CustomType, CustomTypeChild, KindString, TypeDocTypes } from '../types';
import { utils } from '../utils/utils';
import { Comment } from './comment';
import { CustomEnum } from './custom_enum';
import { DocsInfo } from './docs_info';
import { Enum } from './enum';
import { Interface } from './interface';
import { MethodSignature } from './method_signature';
import { Type } from './type';
export interface TypeDefinitionProps {
sectionName: string;
customType: CustomType;
shouldAddId?: boolean;
docsInfo: DocsInfo;
}
export interface TypeDefinitionState {
shouldShowAnchor: boolean;
}
export class TypeDefinition extends React.Component<TypeDefinitionProps, TypeDefinitionState> {
public static defaultProps: Partial<TypeDefinitionProps> = {
shouldAddId: true,
};
constructor(props: TypeDefinitionProps) {
super(props);
this.state = {
shouldShowAnchor: false,
};
}
public render() {
const customType = this.props.customType;
if (!this.props.docsInfo.isPublicType(customType.name)) {
return null; // no-op
}
let typePrefix: string;
let codeSnippet: React.ReactNode;
switch (customType.kindString) {
case KindString.Interface:
typePrefix = 'Interface';
codeSnippet = (
<Interface type={customType} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} />
);
break;
case KindString.Variable:
typePrefix = 'Enum';
codeSnippet = <CustomEnum type={customType} />;
break;
case KindString.Enumeration:
typePrefix = 'Enum';
const enumValues = _.map(customType.children, (c: CustomTypeChild) => {
return {
name: c.name,
defaultValue: c.defaultValue,
};
});
codeSnippet = <Enum values={enumValues} />;
break;
case KindString.TypeAlias:
typePrefix = 'Type Alias';
codeSnippet = (
<span>
<span style={{ color: colors.lightPurple }}>type</span> {customType.name} ={' '}
{customType.type.typeDocType !== TypeDocTypes.Reflection ? (
<Type
type={customType.type}
sectionName={this.props.sectionName}
docsInfo={this.props.docsInfo}
/>
) : (
<MethodSignature
method={customType.type.method}
sectionName={this.props.sectionName}
shouldHideMethodName={true}
shouldUseArrowSyntax={true}
docsInfo={this.props.docsInfo}
/>
)}
</span>
);
break;
default:
throw utils.spawnSwitchErr('type.kindString', customType.kindString);
}
const typeDefinitionAnchorId = `${this.props.sectionName}-${customType.name}`;
return (
<div
id={this.props.shouldAddId ? typeDefinitionAnchorId : ''}
className="pb2"
style={{ overflow: 'hidden', width: '100%' }}
onMouseOver={this._setAnchorVisibility.bind(this, true)}
onMouseOut={this._setAnchorVisibility.bind(this, false)}
>
<AnchorTitle
headerSize={HeaderSizes.H3}
title={`${typePrefix} ${customType.name}`}
id={this.props.shouldAddId ? typeDefinitionAnchorId : ''}
shouldShowAnchor={this.state.shouldShowAnchor}
/>
<div style={{ fontSize: 16 }}>
<pre>
<code className="hljs">{codeSnippet}</code>
</pre>
</div>
<div style={{ maxWidth: 620 }}>
{customType.comment && <Comment comment={customType.comment} className="py2" />}
</div>
</div>
);
}
private _setAnchorVisibility(shouldShowAnchor: boolean) {
this.setState({
shouldShowAnchor,
});
}
}

View File

@@ -0,0 +1,7 @@
declare module 'react-tooltip';
// compare-version declarations
declare function compareVersions(firstVersion: string, secondVersion: string): number;
declare module 'compare-versions' {
export = compareVersions;
}

View File

@@ -0,0 +1,20 @@
export { Documentation } from './components/documentation';
export { DocsInfo } from './components/docs_info';
// Exported to give users of this library added flexibility if they want to build
// a docs page from scratch using the individual components.
export { Badge } from './components/badge';
export { Comment } from './components/comment';
export { CustomEnum } from './components/custom_enum';
export { Enum } from './components/enum';
export { EventDefinition } from './components/event_definition';
export { Interface } from './components/interface';
export { MethodBlock } from './components/method_block';
export { MethodSignature } from './components/method_signature';
export { SourceLink } from './components/source_link';
export { TypeDefinition } from './components/type_definition';
export { Type } from './components/type';
export { DocsInfoConfig, DocAgnosticFormat, DoxityDocObj, DocsMenu, SupportedDocJson, TypeDocNode } from './types';
export { constants } from './utils/constants';

View File

@@ -0,0 +1,266 @@
export interface DocsInfoConfig {
id: string;
type: SupportedDocJson;
displayName: string;
packageUrl: string;
menu: DocsMenu;
sections: SectionsMap;
sectionNameToMarkdown: { [sectionName: string]: string };
visibleConstructors: string[];
subPackageName?: string;
publicTypes?: string[];
sectionNameToModulePath?: { [sectionName: string]: string[] };
menuSubsectionToVersionWhenIntroduced?: { [sectionName: string]: string };
contractsByVersionByNetworkId?: ContractsByVersionByNetworkId;
}
export interface DocsMenu {
[sectionName: string]: string[];
}
export interface SectionsMap {
[sectionName: string]: string;
}
export interface TypeDocType {
type: TypeDocTypes;
value: string;
name: string;
types: TypeDocType[];
typeArguments?: TypeDocType[];
declaration: TypeDocNode;
elementType?: TypeDocType;
}
export interface TypeDocFlags {
isStatic?: boolean;
isOptional?: boolean;
isPublic?: boolean;
}
export interface TypeDocGroup {
title: string;
children: number[];
}
export interface TypeDocNode {
id?: number;
name?: string;
kind?: string;
defaultValue?: string;
kindString?: string;
type?: TypeDocType;
fileName?: string;
line?: number;
comment?: TypeDocNode;
text?: string;
shortText?: string;
returns?: string;
declaration: TypeDocNode;
flags?: TypeDocFlags;
indexSignature?: TypeDocNode | TypeDocNode[]; // TypeDocNode in TypeDoc <V0.9.0, TypeDocNode[] in >V0.9.0
signatures?: TypeDocNode[];
parameters?: TypeDocNode[];
typeParameter?: TypeDocNode[];
sources?: TypeDocNode[];
children?: TypeDocNode[];
groups?: TypeDocGroup[];
}
export enum TypeDocTypes {
Intrinsic = 'intrinsic',
Reference = 'reference',
Array = 'array',
StringLiteral = 'stringLiteral',
Reflection = 'reflection',
Union = 'union',
TypeParameter = 'typeParameter',
Intersection = 'intersection',
Unknown = 'unknown',
}
// Exception: We don't make the values uppercase because these KindString's need to
// match up those returned by TypeDoc
export enum KindString {
Constructor = 'Constructor',
Property = 'Property',
Method = 'Method',
Interface = 'Interface',
TypeAlias = 'Type alias',
Variable = 'Variable',
Function = 'Function',
Enumeration = 'Enumeration',
}
export interface DocAgnosticFormat {
[sectionName: string]: DocSection;
}
export interface DocSection {
comment: string;
constructors: Array<TypescriptMethod | SolidityMethod>;
methods: Array<TypescriptMethod | SolidityMethod>;
properties: Property[];
types: CustomType[];
events?: Event[];
}
export interface TypescriptMethod extends BaseMethod {
source?: Source;
isStatic?: boolean;
typeParameter?: TypeParameter;
}
export interface SolidityMethod extends BaseMethod {
isConstant?: boolean;
isPayable?: boolean;
}
export interface Source {
fileName: string;
line: number;
}
export interface Parameter {
name: string;
comment: string;
isOptional: boolean;
type: Type;
}
export interface TypeParameter {
name: string;
type: Type;
}
export interface Type {
name: string;
typeDocType: TypeDocTypes;
value?: string;
typeArguments?: Type[];
elementType?: ElementType;
types?: Type[];
method?: TypescriptMethod;
}
export interface ElementType {
name: string;
typeDocType: TypeDocTypes;
}
export interface IndexSignature {
keyName: string;
keyType: Type;
valueName: string;
}
export interface CustomType {
name: string;
kindString: string;
type?: Type;
method?: TypescriptMethod;
indexSignature?: IndexSignature;
defaultValue?: string;
comment?: string;
children?: CustomTypeChild[];
}
export interface CustomTypeChild {
name: string;
type?: Type;
defaultValue?: string;
}
export interface Event {
name: string;
eventArgs: EventArg[];
}
export interface EventArg {
isIndexed: boolean;
name: string;
type: Type;
}
export interface Property {
name: string;
type: Type;
source?: Source;
comment?: string;
}
export interface BaseMethod {
isConstructor: boolean;
name: string;
returnComment?: string | undefined;
callPath: string;
parameters: Parameter[];
returnType: Type;
comment?: string;
}
export interface TypeDefinitionByName {
[typeName: string]: CustomType;
}
export enum SupportedDocJson {
Doxity = 'DOXITY',
TypeDoc = 'TYPEDOC',
}
export interface ContractsByVersionByNetworkId {
[version: string]: {
[networkName: string]: {
[contractName: string]: string;
};
};
}
export interface DoxityDocObj {
[contractName: string]: DoxityContractObj;
}
export interface DoxityContractObj {
title: string;
fileName: string;
name: string;
abiDocs: DoxityAbiDoc[];
}
export interface DoxityAbiDoc {
constant: boolean;
inputs: DoxityInput[];
name: string;
outputs: DoxityOutput[];
payable: boolean;
type: string;
details?: string;
return?: string;
}
export interface DoxityOutput {
name: string;
type: string;
}
export interface DoxityInput {
name: string;
type: string;
description: string;
indexed?: boolean;
}
export interface AddressByContractName {
[contractName: string]: string;
}
export interface EnumValue {
name: string;
defaultValue?: string;
}
export enum AbiTypes {
Constructor = 'constructor',
Function = 'function',
Event = 'event',
}

View File

@@ -0,0 +1,9 @@
export const constants = {
TYPES_SECTION_NAME: 'types',
URL_WEB3_DOCS: 'https://github.com/ethereum/wiki/wiki/JavaScript-API',
URL_WEB3_DECODED_LOG_ENTRY_EVENT:
'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L123',
URL_WEB3_LOG_ENTRY_EVENT: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L127',
URL_WEB3_PROVIDER_DOCS: 'https://github.com/0xProject/web3-typescript-typings/blob/f5bcb96/index.d.ts#L150',
URL_BIGNUMBERJS_GITHUB: 'http://mikemcl.github.io/bignumber.js',
};

View File

@@ -0,0 +1,175 @@
import * as _ from 'lodash';
import {
AbiTypes,
DocAgnosticFormat,
DocSection,
DoxityAbiDoc,
DoxityContractObj,
DoxityDocObj,
DoxityInput,
EventArg,
Parameter,
Property,
SolidityMethod,
Type,
TypeDocTypes,
} from '../types';
export const doxityUtils = {
convertToDocAgnosticFormat(doxityDocObj: DoxityDocObj): DocAgnosticFormat {
const docAgnosticFormat: DocAgnosticFormat = {};
_.each(doxityDocObj, (doxityContractObj: DoxityContractObj, contractName: string) => {
const doxityConstructor = _.find(doxityContractObj.abiDocs, (abiDoc: DoxityAbiDoc) => {
return abiDoc.type === AbiTypes.Constructor;
});
const constructors = [];
if (!_.isUndefined(doxityConstructor)) {
const constructor = {
isConstructor: true,
name: doxityContractObj.name,
comment: doxityConstructor.details,
returnComment: doxityConstructor.return,
callPath: '',
parameters: this._convertParameters(doxityConstructor.inputs),
returnType: this._convertType(doxityContractObj.name),
};
constructors.push(constructor);
}
const doxityMethods: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>(
doxityContractObj.abiDocs,
(abiDoc: DoxityAbiDoc) => {
return this._isMethod(abiDoc);
},
);
const methods: SolidityMethod[] = _.map<DoxityAbiDoc, SolidityMethod>(
doxityMethods,
(doxityMethod: DoxityAbiDoc) => {
const outputs = !_.isUndefined(doxityMethod.outputs) ? doxityMethod.outputs : [];
let returnTypeIfExists: Type;
if (outputs.length === 0) {
// no-op. It's already undefined
} else if (outputs.length === 1) {
const outputsType = outputs[0].type;
returnTypeIfExists = this._convertType(outputsType);
} else {
const outputsType = `[${_.map(outputs, output => output.type).join(', ')}]`;
returnTypeIfExists = this._convertType(outputsType);
}
// For ZRXToken, we want to convert it to zrxToken, rather then simply zRXToken
const callPath =
contractName !== 'ZRXToken'
? `${contractName[0].toLowerCase()}${contractName.slice(1)}.`
: `${contractName.slice(0, 3).toLowerCase()}${contractName.slice(3)}.`;
const method = {
isConstructor: false,
isConstant: doxityMethod.constant,
isPayable: doxityMethod.payable,
name: doxityMethod.name,
comment: doxityMethod.details,
returnComment: doxityMethod.return,
callPath,
parameters: this._convertParameters(doxityMethod.inputs),
returnType: returnTypeIfExists,
};
return method;
},
);
const doxityProperties: DoxityAbiDoc[] = _.filter<DoxityAbiDoc>(
doxityContractObj.abiDocs,
(abiDoc: DoxityAbiDoc) => {
return this._isProperty(abiDoc);
},
);
const properties = _.map<DoxityAbiDoc, Property>(doxityProperties, (doxityProperty: DoxityAbiDoc) => {
// We assume that none of our functions return more then a single return value
let typeName = doxityProperty.outputs[0].type;
if (!_.isEmpty(doxityProperty.inputs)) {
// Properties never have more then a single input
typeName = `(${doxityProperty.inputs[0].type} => ${typeName})`;
}
const property = {
name: doxityProperty.name,
type: this._convertType(typeName),
comment: doxityProperty.details,
};
return property;
});
const doxityEvents = _.filter(
doxityContractObj.abiDocs,
(abiDoc: DoxityAbiDoc) => abiDoc.type === AbiTypes.Event,
);
const events = _.map(doxityEvents, doxityEvent => {
const event = {
name: doxityEvent.name,
eventArgs: this._convertEventArgs(doxityEvent.inputs),
};
return event;
});
const docSection: DocSection = {
comment: doxityContractObj.title,
constructors,
methods,
properties,
types: [],
events,
};
docAgnosticFormat[contractName] = docSection;
});
return docAgnosticFormat;
},
_convertParameters(inputs: DoxityInput[]): Parameter[] {
const parameters = _.map(inputs, input => {
const parameter = {
name: input.name,
comment: input.description,
isOptional: false,
type: this._convertType(input.type),
};
return parameter;
});
return parameters;
},
_convertType(typeName: string): Type {
const type = {
name: typeName,
typeDocType: TypeDocTypes.Intrinsic,
};
return type;
},
_isMethod(abiDoc: DoxityAbiDoc) {
if (abiDoc.type !== AbiTypes.Function) {
return false;
}
const hasInputs = !_.isEmpty(abiDoc.inputs);
const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name);
const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase();
const isMethod = hasNamedOutputIfExists && !isNameAllCaps;
return isMethod;
},
_isProperty(abiDoc: DoxityAbiDoc) {
if (abiDoc.type !== AbiTypes.Function) {
return false;
}
const hasInputs = !_.isEmpty(abiDoc.inputs);
const hasNamedOutputIfExists = !hasInputs || !_.isEmpty(abiDoc.inputs[0].name);
const isNameAllCaps = abiDoc.name === abiDoc.name.toUpperCase();
const isProperty = !hasNamedOutputIfExists || isNameAllCaps;
return isProperty;
},
_convertEventArgs(inputs: DoxityInput[]): EventArg[] {
const eventArgs = _.map(inputs, input => {
const eventArg = {
isIndexed: input.indexed,
name: input.name,
type: this._convertType(input.type),
};
return eventArg;
});
return eventArgs;
},
};

View File

@@ -0,0 +1,370 @@
import * as _ from 'lodash';
import { DocsInfo } from '../components/docs_info';
import {
CustomType,
CustomTypeChild,
DocAgnosticFormat,
DocSection,
IndexSignature,
KindString,
Parameter,
Property,
SectionsMap,
Type,
TypeDocNode,
TypeDocType,
TypeParameter,
TypescriptMethod,
} from '../types';
import { utils } from '../utils/utils';
export const typeDocUtils = {
isType(entity: TypeDocNode): boolean {
return (
entity.kindString === KindString.Interface ||
entity.kindString === KindString.Function ||
entity.kindString === KindString.TypeAlias ||
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, '_');
},
getModuleDefinitionsBySectionName(versionDocObj: TypeDocNode, configModulePaths: string[]): TypeDocNode[] {
const moduleDefinitions: TypeDocNode[] = [];
const jsonModules = versionDocObj.children;
_.each(jsonModules, jsonMod => {
_.each(configModulePaths, configModulePath => {
if (_.includes(configModulePath, jsonMod.name)) {
moduleDefinitions.push(jsonMod);
}
});
});
return moduleDefinitions;
},
convertToDocAgnosticFormat(typeDocJson: TypeDocNode, docsInfo: DocsInfo): DocAgnosticFormat {
const subMenus = _.values(docsInfo.getMenu());
const orderedSectionNames = _.flatten(subMenus);
const docAgnosticFormat: DocAgnosticFormat = {};
_.each(orderedSectionNames, sectionName => {
const modulePathsIfExists = docsInfo.getModulePathsIfExists(sectionName);
if (_.isUndefined(modulePathsIfExists)) {
return; // no-op
}
const packageDefinitions = typeDocUtils.getModuleDefinitionsBySectionName(typeDocJson, modulePathsIfExists);
let packageDefinitionWithMergedChildren;
if (_.isEmpty(packageDefinitions)) {
return; // no-op
} else if (packageDefinitions.length === 1) {
packageDefinitionWithMergedChildren = packageDefinitions[0];
} else {
// HACK: For now, if there are two modules to display in a single section,
// we simply concat the children. This works for our limited use-case where
// we want to display types stored in two files under a single section
packageDefinitionWithMergedChildren = packageDefinitions[0];
for (let i = 1; i < packageDefinitions.length; i++) {
packageDefinitionWithMergedChildren.children = [
...packageDefinitionWithMergedChildren.children,
...packageDefinitions[i].children,
];
}
}
// 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 === docsInfo.sections.types) {
entities = packageDefinitionWithMergedChildren.children;
} else {
entities = packageDefinitionWithMergedChildren.children[0].children;
const commentObj = packageDefinitionWithMergedChildren.children[0].comment;
packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment;
}
const docSection = typeDocUtils._convertEntitiesToDocSection(entities, docsInfo, sectionName);
docSection.comment = packageComment;
docAgnosticFormat[sectionName] = docSection;
});
return docAgnosticFormat;
},
_convertEntitiesToDocSection(entities: TypeDocNode[], docsInfo: DocsInfo, 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,
docsInfo.sections,
sectionName,
docsInfo.id,
);
docSection.constructors.push(constructor);
break;
case KindString.Method:
if (entity.flags.isPublic) {
isConstructor = false;
const method = typeDocUtils._convertMethod(
entity,
isConstructor,
docsInfo.sections,
sectionName,
docsInfo.id,
);
docSection.methods.push(method);
}
break;
case KindString.Property:
if (!typeDocUtils.isPrivateOrProtectedProperty(entity.name)) {
const property = typeDocUtils._convertProperty(
entity,
docsInfo.sections,
sectionName,
docsInfo.id,
);
docSection.properties.push(property);
}
break;
case KindString.Interface:
case KindString.Function:
case KindString.Variable:
case KindString.Enumeration:
case KindString.TypeAlias:
if (docsInfo.isPublicType(entity.name)) {
const customType = typeDocUtils._convertCustomType(
entity,
docsInfo.sections,
sectionName,
docsInfo.id,
);
docSection.types.push(customType);
}
break;
default:
throw utils.spawnSwitchErr('kindString', entity.kindString);
}
});
return docSection;
},
_convertCustomType(entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: string): CustomType {
const typeIfExists = !_.isUndefined(entity.type)
? typeDocUtils._convertType(entity.type, sections, sectionName, docId)
: undefined;
const isConstructor = false;
const methodIfExists = !_.isUndefined(entity.declaration)
? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, docId)
: undefined;
const doesIndexSignatureExist = !_.isUndefined(entity.indexSignature);
const isIndexSignatureArray = _.isArray(entity.indexSignature);
// HACK: TypeDoc Versions <0.9.0 indexSignature is of type TypeDocNode[]
// Versions >0.9.0 have it as type TypeDocNode
const indexSignature =
doesIndexSignatureExist && isIndexSignatureArray
? (entity.indexSignature as TypeDocNode[])[0]
: (entity.indexSignature as TypeDocNode);
const indexSignatureIfExists = doesIndexSignatureExist
? typeDocUtils._convertIndexSignature(indexSignature, sections, sectionName, docId)
: 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, sections, sectionName, docId)
: 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,
sections: SectionsMap,
sectionName: string,
docId: string,
): IndexSignature {
const key = entity.parameters[0];
const indexSignature = {
keyName: key.name,
keyType: typeDocUtils._convertType(key.type, sections, sectionName, docId),
valueName: entity.type.name,
};
return indexSignature;
},
_convertProperty(entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: 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, sections, sectionName, docId),
source: {
fileName: source.fileName,
line: source.line,
},
comment: commentIfExists,
};
return property;
},
_convertMethod(
entity: TypeDocNode,
isConstructor: boolean,
sections: SectionsMap,
sectionName: string,
docId: 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;
// 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;
if (isConstructor || entity.name === '__type') {
callPath = '';
// TODO: Get rid of this 0x-specific logic
} else if (docId === 'ZERO_EX_JS') {
const topLevelInterface = isStatic ? 'ZeroEx.' : 'zeroEx.';
callPath =
!_.isUndefined(sections.zeroEx) && sectionName !== sections.zeroEx
? `${topLevelInterface}${sectionName}.`
: topLevelInterface;
} else {
callPath = `${sectionName}.`;
}
const parameters = _.map(signature.parameters, param => {
return typeDocUtils._convertParameter(param, sections, sectionName, docId);
});
const returnType = typeDocUtils._convertType(signature.type, sections, sectionName, docId);
const typeParameter = _.isUndefined(signature.typeParameter)
? undefined
: typeDocUtils._convertTypeParameter(signature.typeParameter[0], sections, sectionName, docId);
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,
sections: SectionsMap,
sectionName: string,
docId: string,
): TypeParameter {
const type = typeDocUtils._convertType(entity.type, sections, sectionName, docId);
const parameter = {
name: entity.name,
type,
};
return parameter;
},
_convertParameter(entity: TypeDocNode, sections: SectionsMap, sectionName: string, docId: 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, sections, sectionName, docId);
const parameter = {
name: entity.name,
comment,
isOptional,
type,
};
return parameter;
},
_convertType(entity: TypeDocType, sections: SectionsMap, sectionName: string, docId: string): Type {
const typeArguments = _.map(entity.typeArguments, typeArgument => {
return typeDocUtils._convertType(typeArgument, sections, sectionName, docId);
});
const types = _.map(entity.types, t => {
return typeDocUtils._convertType(t, sections, sectionName, docId);
});
const isConstructor = false;
const methodIfExists = !_.isUndefined(entity.declaration)
? typeDocUtils._convertMethod(entity.declaration, isConstructor, sections, sectionName, docId)
: 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;
},
};

View File

@@ -0,0 +1,10 @@
export const utils = {
consoleLog(message: string) {
/* tslint:disable */
console.log(message);
/* tslint:enable */
},
spawnSwitchErr(name: string, value: any) {
return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
},
};