Start refactoring docs to remove unnecessary configs given more concise TypeDoc JSON

This commit is contained in:
Fabio Berger
2018-08-01 17:36:37 +02:00
parent 11869122b4
commit 3bdf6004ca
23 changed files with 274 additions and 630 deletions

View File

@@ -1,7 +1,9 @@
import {
AnchorTitle,
colors,
constants as sharedConstants,
EtherscanLinkSuffixes,
HeaderSizes,
MarkdownSection,
NestedSidebarMenu,
Networks,
@@ -32,8 +34,7 @@ import { Badge } from './badge';
import { Comment } from './comment';
import { EventDefinition } from './event_definition';
import { SignatureBlock } from './signature_block';
import { SourceLink } from './source_link';
import { Type } from './type';
import { PropertyBlock } from './property_block';
import { TypeDefinition } from './type_definition';
const networkNameToColor: { [network: string]: string } = {
@@ -129,7 +130,7 @@ export class Documentation extends React.Component<DocumentationProps, Documenta
selectedVersion={this.props.selectedVersion}
versions={this.props.availableVersions}
sidebarHeader={this.props.sidebarHeader}
topLevelMenu={this.props.docsInfo.getMenu(this.props.selectedVersion)}
topLevelMenu={this.props.docsInfo.menu}
menuSubsectionsBySection={menuSubsectionsBySection}
onVersionSelected={this.props.onVersionSelected}
/>
@@ -172,7 +173,7 @@ export class Documentation extends React.Component<DocumentationProps, Documenta
);
}
private _renderDocumentation(): React.ReactNode {
const subMenus = _.values(this.props.docsInfo.getMenu());
const subMenus = _.values(this.props.docsInfo.menu);
const orderedSectionNames = _.flatten(subMenus);
const typeDefinitionByName = this.props.docsInfo.getTypeDefinitionsByName(this.props.docAgnosticFormat);
@@ -258,13 +259,12 @@ export class Documentation extends React.Component<DocumentationProps, Documenta
{this._renderNetworkBadgesIfExists(sectionName)}
</div>
{docSection.comment && <Comment comment={docSection.comment} />}
{!_.isEmpty(docSection.constructors) &&
this.props.docsInfo.isVisibleConstructor(sectionName) && (
<div>
<h2 style={headerStyle}>Constructor</h2>
{this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)}
</div>
)}
{!_.isEmpty(docSection.constructors) && (
<div>
<h2 style={headerStyle}>Constructor</h2>
{this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)}
</div>
)}
{!_.isEmpty(docSection.properties) && (
<div>
<h2 style={headerStyle}>Properties</h2>
@@ -345,20 +345,14 @@ export class Documentation extends React.Component<DocumentationProps, Documenta
}
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>
<PropertyBlock
key={`property-${property.name}-${property.type.name}`}
property={property}
sectionName={sectionName}
docsInfo={this.props.docsInfo}
sourceUrl={this.props.sourceUrl}
selectedVersion={this.props.selectedVersion}
/>
);
}
private _renderSignatureBlocks(

View File

@@ -0,0 +1,70 @@
import { AnchorTitle, HeaderSizes } from '@0xproject/react-shared';
import * as React from 'react';
import { DocsInfo } from '../docs_info';
import { Property } from '../types';
import { constants } from '../utils/constants';
import { Comment } from './comment';
import { Type } from './type';
import { SourceLink } from './source_link';
export interface PropertyBlockProps {
property: Property;
sectionName: string;
docsInfo: DocsInfo;
sourceUrl: string;
selectedVersion: string;
}
export interface PropertyBlockState {
shouldShowAnchor: boolean;
}
export class PropertyBlock extends React.Component<PropertyBlockProps, PropertyBlockState> {
constructor(props: PropertyBlockProps) {
super(props);
this.state = {
shouldShowAnchor: false,
};
}
public render(): React.ReactNode {
const property = this.props.property;
const sectionName = this.props.sectionName;
return (
<div
id={`${this.props.sectionName}-${property.name}`}
className="pb4"
key={`property-${property.name}-${property.type.name}`}
onMouseOver={this._setAnchorVisibility.bind(this, true)}
onMouseOut={this._setAnchorVisibility.bind(this, false)}
>
<div className="pb2" style={{ lineHeight: 1.3 }}>
<AnchorTitle
headerSize={HeaderSizes.H3}
title={property.name}
id={`${sectionName}-${property.name}`}
shouldShowAnchor={this.state.shouldShowAnchor}
/>
</div>
<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 _setAnchorVisibility(shouldShowAnchor: boolean): void {
this.setState({
shouldShowAnchor,
});
}
}

View File

@@ -9,6 +9,7 @@ import { DocsInfo } from '../docs_info';
import { Type as TypeDef, TypeDefinitionByName, TypeDocTypes } from '../types';
import { Signature } from './signature';
import { constants } from '../utils/constants';
import { TypeDefinition } from './type_definition';
export interface TypeProps {
@@ -43,7 +44,7 @@ export function Type(props: TypeProps): any {
<span>
<Type
key={key}
type={arg.elementType}
type={arg}
sectionName={props.sectionName}
typeDefinitionByName={props.typeDefinitionByName}
docsInfo={props.docsInfo}
@@ -142,7 +143,6 @@ export function Type(props: TypeProps): any {
let typeNameUrlIfExists;
let typePrefixIfExists;
let sectionNameIfExists;
if (!_.isUndefined(props.docsInfo.typeConfigs)) {
typeNameUrlIfExists = !_.isUndefined(props.docsInfo.typeConfigs.typeNameToExternalLink)
? props.docsInfo.typeConfigs.typeNameToExternalLink[typeName as string]
@@ -150,9 +150,6 @@ export function Type(props: TypeProps): any {
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 = (
@@ -168,16 +165,12 @@ export function Type(props: TypeProps): any {
);
} else if (
(isReference || isArray) &&
(props.docsInfo.isPublicType(typeName as string) || !_.isUndefined(sectionNameIfExists))
props.typeDefinitionByName &&
props.typeDefinitionByName[typeName as string]
) {
const id = Math.random().toString();
const typeDefinitionAnchorId = _.isUndefined(sectionNameIfExists)
? `${props.sectionName}-${typeName}`
: sectionNameIfExists;
let typeDefinition;
if (props.typeDefinitionByName) {
typeDefinition = props.typeDefinitionByName[typeName as string];
}
const typeDefinitionAnchorId = `${constants.TYPES_SECTION_NAME}-${typeName}`;
let typeDefinition = props.typeDefinitionByName[typeName as string];
typeName = (
<ScrollLink
to={typeDefinitionAnchorId}
@@ -186,18 +179,12 @@ export function Type(props: TypeProps): any {
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>
{sharedUtils.isUserOnMobile() ? (
<span 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',

View File

@@ -37,9 +37,6 @@ export class TypeDefinition extends React.Component<TypeDefinitionProps, TypeDef
}
public render(): React.ReactNode {
const customType = this.props.customType;
if (!this.props.docsInfo.isPublicType(customType.name)) {
return null; // no-op
}
let typePrefix: string;
let codeSnippet: React.ReactNode;

View File

@@ -13,7 +13,7 @@ import {
SectionsMap,
SupportedDocJson,
TypeDefinitionByName,
TypeDocNode,
GeneratedDocJson,
} from './types';
import { doxityUtils } from './utils/doxity_utils';
import { typeDocUtils } from './utils/typedoc_utils';
@@ -32,6 +32,7 @@ export class DocsInfo {
constructor(config: DocsInfoConfig) {
this.id = config.id;
this.type = config.type;
this.menu = config.menu;
this.displayName = config.displayName;
this.packageUrl = config.packageUrl;
this.sections = config.sections;
@@ -40,38 +41,8 @@ export class DocsInfo {
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 doesExistInSelectedVersion = compareVersions(selectedVersion, versionIntroducedIfExists) >= 0;
return doesExistInSelectedVersion;
} else {
return true;
}
});
return finalMenu;
return this._docsInfo.menu;
}
public getMenuSubsectionsBySection(docAgnosticFormat?: DocAgnosticFormat): MenuSubsectionsBySection {
const menuSubsectionsBySection = {} as MenuSubsectionsBySection;
@@ -96,12 +67,18 @@ export class DocsInfo {
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];
const propertiesSortedByName = _.sortBy(docSection.properties, 'name');
const propertyNames = _.map(propertiesSortedByName, m => m.name);
const methodsSortedByName = _.sortBy(docSection.methods, 'name');
const methodNames = _.map(methodsSortedByName, m => m.name);
const sortedFunctionNames = _.sortBy(docSection.functions, 'name');
const functionNames = _.map(sortedFunctionNames, m => m.name);
menuSubsectionsBySection[sectionName] = [...eventNames, ...functionNames, ...methodNames];
menuSubsectionsBySection[sectionName] = [
...eventNames,
...propertyNames,
...functionNames,
...methodNames,
];
}
});
return menuSubsectionsBySection;
@@ -115,14 +92,11 @@ export class DocsInfo {
const typeDefinitionByName = _.keyBy(typeDocSection.types, 'name') as any;
return typeDefinitionByName;
}
public isVisibleConstructor(sectionName: string): boolean {
return _.includes(this._docsInfo.visibleConstructors, sectionName);
}
public convertToDocAgnosticFormat(docObj: DoxityDocObj | TypeDocNode): DocAgnosticFormat {
public convertToDocAgnosticFormat(docObj: DoxityDocObj | GeneratedDocJson): DocAgnosticFormat {
if (this.type === SupportedDocJson.Doxity) {
return doxityUtils.convertToDocAgnosticFormat(docObj as DoxityDocObj);
} else {
return typeDocUtils.convertToDocAgnosticFormat(docObj as TypeDocNode, this);
return typeDocUtils.convertToDocAgnosticFormat(docObj as GeneratedDocJson, this);
}
}
}

View File

@@ -15,6 +15,14 @@ export { Type } from './components/type';
export { DocsInfo } from './docs_info';
export { DocsInfoConfig, DocAgnosticFormat, DoxityDocObj, DocsMenu, SupportedDocJson, TypeDocNode } from './types';
export {
DocsInfoConfig,
DocAgnosticFormat,
DoxityDocObj,
DocsMenu,
SupportedDocJson,
TypeDocNode,
GeneratedDocJson,
} from './types';
export { constants } from './utils/constants';

View File

@@ -10,18 +10,13 @@ export interface DocsInfoConfig {
menu: DocsMenu;
sections: SectionsMap;
sectionNameToMarkdownByVersion: SectionNameToMarkdownByVersion;
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 {
@@ -292,3 +287,17 @@ export enum AbiTypes {
Function = 'function',
Event = 'event',
}
export interface ExportNameToTypedocName {
[exportName: string]: string;
}
export interface Metadata {
exportPathToTypedocName: ExportNameToTypedocName;
exportPathOrder: string[];
}
export interface GeneratedDocJson {
metadata: Metadata;
typedocJson: TypeDocNode;
}

View File

@@ -19,8 +19,11 @@ import {
TypeParameter,
TypescriptFunction,
TypescriptMethod,
GeneratedDocJson,
} from '../types';
import { constants } from './constants';
export const typeDocUtils = {
isType(entity: TypeDocNode): boolean {
return (
@@ -55,62 +58,68 @@ export const typeDocUtils = {
});
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,
];
}
}
convertToDocAgnosticFormat(generatedDocJson: GeneratedDocJson, docsInfo: DocsInfo): DocAgnosticFormat {
const exportPathOrder = generatedDocJson.metadata.exportPathOrder;
const exportPathToTypedocName = generatedDocJson.metadata.exportPathToTypedocName;
const typeDocJson = generatedDocJson.typedocJson;
let entities;
let packageComment = '';
// HACK: We assume 1 exported class per file
const classChildren = _.filter(packageDefinitionWithMergedChildren.children, (child: TypeDocNode) => {
return child.kindString === KindString.Class;
});
if (classChildren.length > 1 && sectionName !== 'types') {
throw new Error('`react-docs` only supports projects with 1 exported class per file');
}
const isClassExport = packageDefinitionWithMergedChildren.children[0].kindString === KindString.Class;
const isObjectLiteralExport =
packageDefinitionWithMergedChildren.children[0].kindString === KindString.ObjectLiteral;
if (isClassExport) {
entities = packageDefinitionWithMergedChildren.children[0].children;
const commentObj = packageDefinitionWithMergedChildren.children[0].comment;
packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment;
} else if (isObjectLiteralExport) {
entities = packageDefinitionWithMergedChildren.children[0].children;
const commentObj = packageDefinitionWithMergedChildren.children[0].comment;
packageComment = !_.isUndefined(commentObj) ? commentObj.shortText : packageComment;
} else {
entities = packageDefinitionWithMergedChildren.children;
}
const docSection = typeDocUtils._convertEntitiesToDocSection(entities, docsInfo, sectionName);
docSection.comment = packageComment;
docAgnosticFormat[sectionName] = docSection;
const typeDocNameOrder = _.map(exportPathOrder, exportPath => {
return exportPathToTypedocName[exportPath];
});
const docAgnosticFormat: DocAgnosticFormat = {};
const typeEntities: TypeDocNode[] = [];
_.each(typeDocNameOrder, typeDocName => {
const fileChildIndex = _.findIndex(typeDocJson.children, child => child.name === typeDocName);
const fileChild = typeDocJson.children[fileChildIndex];
let sectionName: string;
_.each(fileChild.children, (child, j) => {
switch (child.kindString) {
case KindString.Class:
case KindString.ObjectLiteral: {
sectionName = child.name;
docsInfo.sections[sectionName] = sectionName;
docsInfo.menu[sectionName] = [sectionName];
const entities = child.children;
const commentObj = child.comment;
const sectionComment = !_.isUndefined(commentObj) ? commentObj.shortText : '';
const docSection = typeDocUtils._convertEntitiesToDocSection(entities, docsInfo, sectionName);
docSection.comment = sectionComment;
docAgnosticFormat[sectionName] = docSection;
break;
}
case KindString.Function: {
sectionName = child.name;
docsInfo.sections[sectionName] = sectionName;
docsInfo.menu[sectionName] = [sectionName];
const entities = [child];
const commentObj = child.comment;
const SectionComment = !_.isUndefined(commentObj) ? commentObj.shortText : '';
const docSection = typeDocUtils._convertEntitiesToDocSection(entities, docsInfo, sectionName);
docSection.comment = SectionComment;
docAgnosticFormat[sectionName] = docSection;
break;
}
case KindString.Interface:
case KindString.Variable:
case KindString.Enumeration:
case KindString.TypeAlias:
typeEntities.push(child);
break;
default:
throw errorUtils.spawnSwitchErr('kindString', child.kindString);
}
});
});
docsInfo.sections[constants.TYPES_SECTION_NAME] = constants.TYPES_SECTION_NAME;
docsInfo.menu[constants.TYPES_SECTION_NAME] = [constants.TYPES_SECTION_NAME];
const docSection = typeDocUtils._convertEntitiesToDocSection(
typeEntities,
docsInfo,
constants.TYPES_SECTION_NAME,
);
docAgnosticFormat[constants.TYPES_SECTION_NAME] = docSection;
return docAgnosticFormat;
},
_convertEntitiesToDocSection(entities: TypeDocNode[], docsInfo: DocsInfo, sectionName: string): DocSection {
@@ -175,18 +184,16 @@ export const typeDocUtils = {
case KindString.Variable:
case KindString.Enumeration:
case KindString.TypeAlias:
if (docsInfo.isPublicType(entity.name)) {
const customType = typeDocUtils._convertCustomType(
entity,
docsInfo.sections,
sectionName,
docsInfo.id,
);
const seenTypeNames = _.map(docSection.types, t => t.name);
const isUnseen = !_.includes(seenTypeNames, customType.name);
if (isUnseen) {
docSection.types.push(customType);
}
const customType = typeDocUtils._convertCustomType(
entity,
docsInfo.sections,
sectionName,
docsInfo.id,
);
const seenTypeNames = _.map(docSection.types, t => t.name);
const isUnseen = !_.includes(seenTypeNames, customType.name);
if (isUnseen) {
docSection.types.push(customType);
}
break;