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,5 @@
.*
yarn-error.log
/src/
/scripts/
tsconfig.json

View File

@@ -0,0 +1,3 @@
# CHANGELOG
## vX.X.X - _TBD, 2018_

View File

@@ -0,0 +1,47 @@
## @0xproject/react-docs
A full-page React component for rendering beautiful documentation generated with [TypeDoc](http://typedoc.org/) or [Doxity](https://github.com/0xproject/doxity).
## Installation
```bash
yarn add @0xproject/react-docs
```
## Contributing
We strongly encourage that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository.
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
### Install Dependencies
If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
```bash
yarn config set workspaces-experimental true
```
Then install dependencies
```bash
yarn install
```
### Build
```bash
yarn build
```
### Lint
```bash
yarn lint
```
### Run Tests
```bash
yarn test
```

View File

@@ -0,0 +1,37 @@
{
"name": "@0xproject/react-docs",
"version": "0.0.1",
"description": "React documentation component for rendering TypeDoc & Doxity generated JSON",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
"lint": "tslint --project . 'src/ts/**/*.ts' 'src/ts/**/*.tsx'",
"build": "tsc",
"build:watch": "tsc -w",
"clean": "shx rm -rf lib"
},
"author": "Fabio Berger",
"license": "MIT",
"devDependencies": {
"@0xproject/tslint-config": "^0.4.9",
"@types/lodash": "^4.14.86",
"@types/node": "^8.0.53",
"@types/material-ui": "0.18.0",
"@types/react": "^15.0.15",
"@types/react-dom": "^0.14.23",
"shx": "^0.2.2",
"tslint": "^5.9.1",
"typescript": "2.7.1"
},
"dependencies": {
"@0xproject/react-shared": "^0.0.1",
"basscss": "^8.0.3",
"compare-versions": "^3.0.1",
"react-tooltip": "^3.2.7",
"material-ui": "^0.17.1",
"react": "15.6.1",
"react-dom": "15.6.1",
"lodash": "^4.17.4",
"react-tap-event-plugin": "^2.0.1"
}
}

View File

@@ -0,0 +1,5 @@
const postpublish_utils = require('../../../scripts/postpublish_utils');
const packageJSON = require('../package.json');
const subPackageName = packageJSON.name;
postpublish_utils.standardPostPublishAsync(subPackageName);

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}`);
},
};

View File

@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "./lib/",
"jsx": "react",
"baseUrl": "./",
"strictNullChecks": false,
"noImplicitThis": false,
"paths": {
"*": ["node_modules/@types/*", "*"]
}
},
"include": ["./src/ts/**/*"]
}

View File

@@ -0,0 +1,9 @@
{
"extends": ["@0xproject/tslint-config"],
"rules": {
"no-implicit-dependencies": false,
"no-object-literal-type-assertion": false,
"completed-docs": false,
"prefer-function-over-method": false
}
}