diff options
author | Brandon Millman <brandon.millman@gmail.com> | 2018-03-09 00:43:16 +0800 |
---|---|---|
committer | Brandon Millman <brandon.millman@gmail.com> | 2018-03-09 00:43:16 +0800 |
commit | 098dae9a7e57385505f0f272fd76d44f43fa1e34 (patch) | |
tree | bcd47532910e5804746ec88c8d9f6b85c157d6e1 /packages/react-docs/src | |
parent | 5b5037a844f49c17deeaf695fc8ed57832038da6 (diff) | |
parent | aaa7affa46450bb48639e9b90c2a2e8adb28826c (diff) | |
download | dexon-0x-contracts-098dae9a7e57385505f0f272fd76d44f43fa1e34.tar.gz dexon-0x-contracts-098dae9a7e57385505f0f272fd76d44f43fa1e34.tar.zst dexon-0x-contracts-098dae9a7e57385505f0f272fd76d44f43fa1e34.zip |
Merge branch 'development' into feature/sra-reporter
* development: (68 commits)
Update list of packages and organize them alphabetically
Fix prettier issues
Add support for going back to previous hashes via the browser back button to wiki
Scroll to previous hashed elements when user clicks back button
Add back strict null checks to react-shared package and fix issues
remove ability to have implicit dependencies and add missing deps
update license
remove no-implicit-this
Add example & screenshot to npmignore
Remove `;` to be nice to windows users
Use unencoded @ symbol, browser will fix
Fix external type links
Add comment about commented out CSS exception
Update prettier since the previous version had a bug when dealing with css files
Fix css files with prettier
Added base-contract package to README
Prettify test jsons
Update yarn.lock
Improve README
Feedback
...
Diffstat (limited to 'packages/react-docs/src')
20 files changed, 2309 insertions, 0 deletions
diff --git a/packages/react-docs/src/ts/components/badge.tsx b/packages/react-docs/src/ts/components/badge.tsx new file mode 100644 index 000000000..b342f2dca --- /dev/null +++ b/packages/react-docs/src/ts/components/badge.tsx @@ -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, + }); + } +} diff --git a/packages/react-docs/src/ts/components/comment.tsx b/packages/react-docs/src/ts/components/comment.tsx new file mode 100644 index 000000000..0d63d4d31 --- /dev/null +++ b/packages/react-docs/src/ts/components/comment.tsx @@ -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; diff --git a/packages/react-docs/src/ts/components/custom_enum.tsx b/packages/react-docs/src/ts/components/custom_enum.tsx new file mode 100644 index 000000000..deb33ff1d --- /dev/null +++ b/packages/react-docs/src/ts/components/custom_enum.tsx @@ -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> + ); +} diff --git a/packages/react-docs/src/ts/components/documentation.tsx b/packages/react-docs/src/ts/components/documentation.tsx new file mode 100644 index 000000000..b46358159 --- /dev/null +++ b/packages/react-docs/src/ts/components/documentation.tsx @@ -0,0 +1,375 @@ +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 { DocsInfo } from '../docs_info'; +import { + AddressByContractName, + DocAgnosticFormat, + DoxityDocObj, + Event, + Property, + SolidityMethod, + SupportedDocJson, + TypeDefinitionByName, + TypescriptMethod, +} from '../types'; +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; + +import { Badge } from './badge'; +import { Comment } from './comment'; +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 networkNameToColor: { [network: string]: string } = { + [Networks.Kovan]: colors.purple, + [Networks.Ropsten]: colors.red, + [Networks.Mainnet]: colors.turquois, + [Networks.Rinkeby]: colors.darkYellow, +}; + +export interface DocumentationProps { + selectedVersion: string; + availableVersions: string[]; + docsInfo: DocsInfo; + sourceUrl: string; + onVersionSelected: (semver: string) => void; + docAgnosticFormat?: DocAgnosticFormat; + sidebarHeader?: React.ReactNode; + topBarHeight?: number; +} + +export interface DocumentationState { + isHoveringSidebar: boolean; +} + +export class Documentation extends React.Component<DocumentationProps, DocumentationState> { + public static defaultProps: Partial<DocumentationProps> = { + topBarHeight: 0, + }; + constructor(props: DocumentationProps) { + super(props); + this.state = { + isHoveringSidebar: false, + }; + } + public componentDidMount() { + window.addEventListener('hashchange', this._onHashChanged.bind(this), false); + } + public componentWillUnmount() { + window.removeEventListener('hashchange', this._onHashChanged.bind(this), false); + } + public componentDidUpdate(prevProps: DocumentationProps, prevState: DocumentationState) { + if (!_.isEqual(prevProps.docAgnosticFormat, this.props.docAgnosticFormat)) { + const hash = window.location.hash.slice(1); + sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); + } + } + public render() { + const styles: Styles = { + mainContainers: { + position: 'absolute', + top: 1, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: `calc(100vh - ${this.props.topBarHeight}px)`, + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, + }; + const menuSubsectionsBySection = this.props.docsInfo.getMenuSubsectionsBySection(this.props.docAgnosticFormat); + return ( + <div> + {_.isUndefined(this.props.docAgnosticFormat) ? ( + this._renderLoading(styles.mainContainers) + ) : ( + <div style={{ width: '100%', height: '100%', backgroundColor: colors.gray40 }}> + <div + className="mx-auto max-width-4 flex" + style={{ color: colors.grey800, height: `calc(100vh - ${this.props.topBarHeight}px)` }} + > + <div + className="relative sm-hide xs-hide" + style={{ width: '36%', height: `calc(100vh - ${this.props.topBarHeight}px)` }} + > + <div + className="border-right absolute" + style={{ + ...styles.menuContainer, + ...styles.mainContainers, + height: `calc(100vh - ${this.props.topBarHeight}px)`, + overflow: this.state.isHoveringSidebar ? 'auto' : 'hidden', + }} + onMouseEnter={this._onSidebarHover.bind(this)} + onMouseLeave={this._onSidebarHoverOff.bind(this)} + > + <NestedSidebarMenu + selectedVersion={this.props.selectedVersion} + versions={this.props.availableVersions} + sidebarHeader={this.props.sidebarHeader} + topLevelMenu={this.props.docsInfo.getMenu(this.props.selectedVersion)} + menuSubsectionsBySection={menuSubsectionsBySection} + onVersionSelected={this.props.onVersionSelected} + /> + </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(mainContainersStyles: React.CSSProperties) { + return ( + <div className="col col-12" style={mainContainersStyles}> + <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} + /> + ); + }); + const headerStyle: React.CSSProperties = { + fontWeight: 100, + }; + 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 style={headerStyle}>Constructor</h2> + {this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)} + </div> + )} + {docSection.properties.length > 0 && ( + <div> + <h2 style={headerStyle}>Properties</h2> + <div>{propertyDefs}</div> + </div> + )} + {docSection.methods.length > 0 && ( + <div> + <h2 style={headerStyle}>Methods</h2> + <div>{methodDefs}</div> + </div> + )} + {!_.isUndefined(docSection.events) && + docSection.events.length > 0 && ( + <div> + <h2 style={headerStyle}>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.selectedVersion + ]; + 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 ${constants.TYPE_TO_SYNTAX[this.props.docsInfo.type]}`}> + {property.name}: + <Type type={property.type} sectionName={sectionName} docsInfo={this.props.docsInfo} /> + </code> + {property.source && ( + <SourceLink + version={this.props.selectedVersion} + 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.selectedVersion} + docsInfo={this.props.docsInfo} + sourceUrl={this.props.sourceUrl} + /> + ); + } + private _onSidebarHover(event: React.FormEvent<HTMLInputElement>) { + this.setState({ + isHoveringSidebar: true, + }); + } + private _onSidebarHoverOff() { + this.setState({ + isHoveringSidebar: false, + }); + } + private _onHashChanged(event: any) { + const hash = window.location.hash.slice(1); + sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); + } +} diff --git a/packages/react-docs/src/ts/components/enum.tsx b/packages/react-docs/src/ts/components/enum.tsx new file mode 100644 index 000000000..37f82f26e --- /dev/null +++ b/packages/react-docs/src/ts/components/enum.tsx @@ -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> + ); +} diff --git a/packages/react-docs/src/ts/components/event_definition.tsx b/packages/react-docs/src/ts/components/event_definition.tsx new file mode 100644 index 000000000..67729ac87 --- /dev/null +++ b/packages/react-docs/src/ts/components/event_definition.tsx @@ -0,0 +1,84 @@ +import { AnchorTitle, colors, HeaderSizes } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { DocsInfo } from '../docs_info'; +import { Event, EventArg } from '../types'; + +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 solidity">{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, + }); + } +} diff --git a/packages/react-docs/src/ts/components/interface.tsx b/packages/react-docs/src/ts/components/interface.tsx new file mode 100644 index 000000000..01f4942ef --- /dev/null +++ b/packages/react-docs/src/ts/components/interface.tsx @@ -0,0 +1,63 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { DocsInfo } from '../docs_info'; +import { CustomType, TypeDocTypes } from '../types'; + +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> + ); +} diff --git a/packages/react-docs/src/ts/components/method_block.tsx b/packages/react-docs/src/ts/components/method_block.tsx new file mode 100644 index 000000000..44a1db8af --- /dev/null +++ b/packages/react-docs/src/ts/components/method_block.tsx @@ -0,0 +1,150 @@ +import { AnchorTitle, colors, HeaderSizes, Styles } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { DocsInfo } from '../docs_info'; +import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from '../types'; +import { constants } from '../utils/constants'; +import { typeDocUtils } from '../utils/typedoc_utils'; + +import { Comment } from './comment'; +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 ${constants.TYPE_TO_SYNTAX[this.props.docsInfo.type]}`}> + <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, + }); + } +} diff --git a/packages/react-docs/src/ts/components/method_signature.tsx b/packages/react-docs/src/ts/components/method_signature.tsx new file mode 100644 index 000000000..1400182ea --- /dev/null +++ b/packages/react-docs/src/ts/components/method_signature.tsx @@ -0,0 +1,128 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +import { DocsInfo } from '../docs_info'; +import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from '../types'; +import { constants } from '../utils/constants'; + +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; +} diff --git a/packages/react-docs/src/ts/components/source_link.tsx b/packages/react-docs/src/ts/components/source_link.tsx new file mode 100644 index 000000000..89956a507 --- /dev/null +++ b/packages/react-docs/src/ts/components/source_link.tsx @@ -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> + ); +} diff --git a/packages/react-docs/src/ts/components/type.tsx b/packages/react-docs/src/ts/components/type.tsx new file mode 100644 index 000000000..56425a5df --- /dev/null +++ b/packages/react-docs/src/ts/components/type.tsx @@ -0,0 +1,227 @@ +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 { DocsInfo } from '../docs_info'; +import { Type as TypeDef, TypeDefinitionByName, TypeDocTypes } from '../types'; +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; + +import { TypeDefinition } from './type_definition'; + +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]; + }); + + let typeNameUrlIfExists; + let typePrefixIfExists; + let sectionNameIfExists; + if (!_.isUndefined(props.docsInfo.typeConfigs)) { + typeNameUrlIfExists = !_.isUndefined(props.docsInfo.typeConfigs.typeNameToExternalLink) + ? props.docsInfo.typeConfigs.typeNameToExternalLink[typeName as string] + : undefined; + typePrefixIfExists = !_.isUndefined(props.docsInfo.typeConfigs.typeNameToPrefix) + ? props.docsInfo.typeConfigs.typeNameToPrefix[typeName as string] + : undefined; + sectionNameIfExists = !_.isUndefined(props.docsInfo.typeConfigs.typeNameToDocSection) + ? props.docsInfo.typeConfigs.typeNameToDocSection[typeName as string] + : undefined; + } + 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> + ); +} diff --git a/packages/react-docs/src/ts/components/type_definition.tsx b/packages/react-docs/src/ts/components/type_definition.tsx new file mode 100644 index 000000000..68ef4c465 --- /dev/null +++ b/packages/react-docs/src/ts/components/type_definition.tsx @@ -0,0 +1,131 @@ +import { AnchorTitle, colors, HeaderSizes } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { DocsInfo } from '../docs_info'; +import { CustomType, CustomTypeChild, KindString, TypeDocTypes } from '../types'; +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; + +import { Comment } from './comment'; +import { CustomEnum } from './custom_enum'; +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 ${constants.TYPE_TO_SYNTAX[this.props.docsInfo.type]}`}> + {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, + }); + } +} diff --git a/packages/react-docs/src/ts/docs_info.ts b/packages/react-docs/src/ts/docs_info.ts new file mode 100644 index 000000000..68bddef06 --- /dev/null +++ b/packages/react-docs/src/ts/docs_info.ts @@ -0,0 +1,123 @@ +import { MenuSubsectionsBySection } from '@0xproject/react-shared'; +import compareVersions = require('compare-versions'); +import * as _ from 'lodash'; + +import { + ContractsByVersionByNetworkId, + DocAgnosticFormat, + DocsInfoConfig, + DocsInfoTypeConfigs, + 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; + public typeConfigs: DocsInfoTypeConfigs; + 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.typeConfigs = config.typeConfigs; + this._docsInfo = config; + } + public isPublicType(typeName: string): boolean { + if (_.isUndefined(this._docsInfo.typeConfigs.publicTypes)) { + return false; + } + const isPublic = _.includes(this._docsInfo.typeConfigs.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); + } + } +} diff --git a/packages/react-docs/src/ts/globals.d.ts b/packages/react-docs/src/ts/globals.d.ts new file mode 100644 index 000000000..c7cd53854 --- /dev/null +++ b/packages/react-docs/src/ts/globals.d.ts @@ -0,0 +1,14 @@ +declare module 'react-tooltip'; + +// compare-version declarations +declare function compareVersions(firstVersion: string, secondVersion: string): number; +declare module 'compare-versions' { + export = compareVersions; +} + +declare module '*.json' { + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ +} diff --git a/packages/react-docs/src/ts/index.ts b/packages/react-docs/src/ts/index.ts new file mode 100644 index 000000000..a62c91376 --- /dev/null +++ b/packages/react-docs/src/ts/index.ts @@ -0,0 +1,20 @@ +// 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 { Documentation } from './components/documentation'; +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 { DocsInfo } from './docs_info'; + +export { DocsInfoConfig, DocAgnosticFormat, DoxityDocObj, DocsMenu, SupportedDocJson, TypeDocNode } from './types'; + +export { constants } from './utils/constants'; diff --git a/packages/react-docs/src/ts/types.ts b/packages/react-docs/src/ts/types.ts new file mode 100644 index 000000000..d192af313 --- /dev/null +++ b/packages/react-docs/src/ts/types.ts @@ -0,0 +1,272 @@ +export interface DocsInfoConfig { + id: string; + type: SupportedDocJson; + displayName: string; + packageUrl: string; + menu: DocsMenu; + sections: SectionsMap; + sectionNameToMarkdown: { [sectionName: string]: string }; + visibleConstructors: string[]; + sectionNameToModulePath?: { [sectionName: string]: string[] }; + menuSubsectionToVersionWhenIntroduced?: { [sectionName: string]: string }; + contractsByVersionByNetworkId?: ContractsByVersionByNetworkId; + typeConfigs?: DocsInfoTypeConfigs; +} + +export interface DocsInfoTypeConfigs { + typeNameToExternalLink?: { [typeName: string]: string }; + publicTypes?: string[]; + typeNameToPrefix?: { [typeName: string]: string }; + typeNameToDocSection?: { [typeName: string]: string }; +} + +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', +} diff --git a/packages/react-docs/src/ts/utils/constants.ts b/packages/react-docs/src/ts/utils/constants.ts new file mode 100644 index 000000000..c3c74fd11 --- /dev/null +++ b/packages/react-docs/src/ts/utils/constants.ts @@ -0,0 +1,9 @@ +import { SupportedDocJson } from '../types'; + +export const constants = { + TYPES_SECTION_NAME: 'types', + TYPE_TO_SYNTAX: { + [SupportedDocJson.Doxity]: 'solidity', + [SupportedDocJson.TypeDoc]: 'typescript', + } as { [supportedDocType: string]: string }, +}; diff --git a/packages/react-docs/src/ts/utils/doxity_utils.ts b/packages/react-docs/src/ts/utils/doxity_utils.ts new file mode 100644 index 000000000..26dea6966 --- /dev/null +++ b/packages/react-docs/src/ts/utils/doxity_utils.ts @@ -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; + }, +}; diff --git a/packages/react-docs/src/ts/utils/typedoc_utils.ts b/packages/react-docs/src/ts/utils/typedoc_utils.ts new file mode 100644 index 000000000..e4cea1e40 --- /dev/null +++ b/packages/react-docs/src/ts/utils/typedoc_utils.ts @@ -0,0 +1,370 @@ +import * as _ from 'lodash'; + +import { DocsInfo } from '../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; + }, +}; diff --git a/packages/react-docs/src/ts/utils/utils.ts b/packages/react-docs/src/ts/utils/utils.ts new file mode 100644 index 000000000..8e1a80a44 --- /dev/null +++ b/packages/react-docs/src/ts/utils/utils.ts @@ -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}`); + }, +}; |