1import { css } from '@emotion/core';
2import { theme } from '@expo/styleguide';
3import React from 'react';
4import ReactMarkdown from 'react-markdown';
5
6import { Code, InlineCode } from '~/components/base/code';
7import { H4 } from '~/components/base/headings';
8import { InternalLink } from '~/components/base/link';
9import { LI, UL } from '~/components/base/list';
10import { B, P, Quote } from '~/components/base/paragraph';
11import {
12  CommentData,
13  MethodParamData,
14  TypeDefinitionData,
15  TypeDefinitionTypesData,
16} from '~/components/plugins/api/APIDataTypes';
17
18export enum TypeDocKind {
19  Enum = 4,
20  Variable = 32,
21  Function = 64,
22  Class = 128,
23  Interface = 256,
24  Property = 1024,
25  TypeAlias = 4194304,
26}
27
28export type MDRenderers = React.ComponentProps<typeof ReactMarkdown>['renderers'];
29
30export const mdRenderers: MDRenderers = {
31  blockquote: ({ children }) => (
32    <Quote>
33      {React.Children.map(children, child =>
34        child.type.name === 'paragraph' ? child.props.children : child
35      )}
36    </Quote>
37  ),
38  code: ({ value, language }) => <Code className={`language-${language}`}>{value}</Code>,
39  heading: ({ children }) => <H4>{children}</H4>,
40  inlineCode: ({ value }) => <InlineCode>{value}</InlineCode>,
41  list: ({ children }) => <UL>{children}</UL>,
42  listItem: ({ children }) => <LI>{children}</LI>,
43  link: ({ href, children }) => <InternalLink href={href}>{children}</InternalLink>,
44  paragraph: ({ children }) => (children ? <P>{children}</P> : null),
45  strong: ({ children }) => <B>{children}</B>,
46  text: ({ value }) => (value ? <span>{value}</span> : null),
47};
48
49export const mdInlineRenderers: MDRenderers = {
50  ...mdRenderers,
51  paragraph: ({ children }) => (children ? <span>{children}</span> : null),
52};
53
54const nonLinkableTypes = ['Date', 'Uint8Array'];
55
56export const resolveTypeName = ({
57  elementType,
58  name,
59  type,
60  types,
61  typeArguments,
62  declaration,
63}: TypeDefinitionData): string | JSX.Element => {
64  if (name) {
65    if (type === 'reference') {
66      if (typeArguments) {
67        if (name === 'Promise') {
68          return (
69            <span>
70              {'Promise<'}
71              {typeArguments.map(resolveTypeName)}
72              {'>'}
73            </span>
74          );
75        } else {
76          return `${typeArguments.map(resolveTypeName)}`;
77        }
78      } else {
79        if (nonLinkableTypes.includes(name)) {
80          return name;
81        } else {
82          return (
83            <InternalLink href={`#${name.toLowerCase()}`} key={`type-link-${name}`}>
84              {name}
85            </InternalLink>
86          );
87        }
88      }
89    } else {
90      return name;
91    }
92  } else if (elementType?.name) {
93    if (type === 'array') {
94      return elementType.name + '[]';
95    }
96    return elementType.name + type;
97  } else if (type === 'union' && types?.length) {
98    return types
99      .map((t: TypeDefinitionTypesData) =>
100        t.type === 'array' ? `${t.elementType?.name}[]` : `${t.name || t.value}`
101      )
102      .join(' | ');
103  } else if (declaration?.signatures) {
104    return `() => ${resolveTypeName(declaration.signatures[0].type)}`;
105  }
106  return 'undefined';
107};
108
109export const renderParam = ({ comment, name, type }: MethodParamData): JSX.Element => (
110  <LI key={`param-${name}`}>
111    <B>
112      {name} (<InlineCode>{resolveTypeName(type)}</InlineCode>)
113    </B>
114    <CommentTextBlock comment={comment} renderers={mdInlineRenderers} withDash />
115  </LI>
116);
117
118export type CommentTextBlockProps = {
119  comment?: CommentData;
120  renderers?: MDRenderers;
121  withDash?: boolean;
122};
123
124export const CommentTextBlock: React.FC<CommentTextBlockProps> = ({
125  comment,
126  renderers = mdRenderers,
127  withDash,
128}) => {
129  const shortText = comment?.shortText?.trim().length ? (
130    <ReactMarkdown renderers={renderers}>{comment.shortText}</ReactMarkdown>
131  ) : null;
132  const text = comment?.text?.trim().length ? (
133    <ReactMarkdown renderers={renderers}>{comment.text}</ReactMarkdown>
134  ) : null;
135  return (
136    <>
137      {withDash && (shortText || text) ? ' - ' : null}
138      {shortText}
139      {text}
140    </>
141  );
142};
143
144export const STYLES_OPTIONAL = css`
145  color: ${theme.text.secondary};
146  font-size: 90%;
147  padding-top: 22px;
148`;
149