1import { css } from '@emotion/react';
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 Link 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 }) => <Link href={href}>{children}</Link>,
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', 'T', 'TaskOptions', 'Uint8Array'];
55
56export const resolveTypeName = ({
57  elementType,
58  name,
59  type,
60  types,
61  typeArguments,
62  declaration,
63}: TypeDefinitionData): string | JSX.Element | (string | JSX.Element)[] => {
64  if (name) {
65    if (type === 'reference') {
66      if (typeArguments) {
67        if (name === 'Promise') {
68          return (
69            <span>
70              {name}&lt;{typeArguments.map(resolveTypeName)}&gt;
71            </span>
72          );
73        } else if (name === 'Record') {
74          return (
75            <span>
76              {name}&lt;{typeArguments.map(resolveTypeName).join(',')}&gt;
77            </span>
78          );
79        } else {
80          return `${typeArguments.map(resolveTypeName)}`;
81        }
82      } else {
83        if (nonLinkableTypes.includes(name)) {
84          return name;
85        } else {
86          return (
87            <Link href={`#${name.toLowerCase()}`} key={`type-link-${name}`}>
88              {name}
89            </Link>
90          );
91        }
92      }
93    } else {
94      return name;
95    }
96  } else if (elementType?.name) {
97    if (elementType.type === 'reference') {
98      return (
99        <Link href={`#${elementType.name?.toLowerCase()}`} key={`type-link-${elementType.name}`}>
100          {elementType.name}
101          {type === 'array' && '[]'}
102        </Link>
103      );
104    }
105    if (type === 'array') {
106      return elementType.name + '[]';
107    }
108    return elementType.name + type;
109  } else if (type === 'union' && types?.length) {
110    return types
111      .map((t: TypeDefinitionTypesData) =>
112        t.type === 'reference' ? (
113          <Link href={`#${t.name?.toLowerCase()}`} key={`type-link-${t.name}`}>
114            {t.name}
115          </Link>
116        ) : t.type === 'array' ? (
117          `${t.elementType?.name}[]`
118        ) : t.type === 'literal' && typeof t.value === 'string' ? (
119          `'${t.name || t.value}'`
120        ) : (
121          `${t.name || t.value}`
122        )
123      )
124      .map((valueToRender, index) => (
125        <span key={`union-type-${index}`}>
126          {valueToRender}
127          {index + 1 !== types.length && ' | '}
128        </span>
129      ));
130  } else if (declaration?.signatures) {
131    const baseSignature = declaration.signatures[0];
132    if (baseSignature?.parameters?.length) {
133      return (
134        <>
135          (
136          {baseSignature.parameters?.map((param, index) => (
137            <span key={`param-${index}-${param.name}`}>
138              {param.name}: {resolveTypeName(param.type)}
139              {index + 1 !== baseSignature.parameters.length && ', '}
140            </span>
141          ))}
142          ) {'=>'} {resolveTypeName(baseSignature.type)}
143        </>
144      );
145    } else {
146      return `() => ${resolveTypeName(baseSignature.type)}`;
147    }
148  }
149  return 'undefined';
150};
151
152export const renderParam = ({ comment, name, type }: MethodParamData): JSX.Element => (
153  <LI key={`param-${name}`}>
154    <B>
155      {name} (<InlineCode>{resolveTypeName(type)}</InlineCode>)
156    </B>
157    <CommentTextBlock comment={comment} renderers={mdInlineRenderers} withDash />
158  </LI>
159);
160
161export type CommentTextBlockProps = {
162  comment?: CommentData;
163  renderers?: MDRenderers;
164  withDash?: boolean;
165};
166
167export const CommentTextBlock: React.FC<CommentTextBlockProps> = ({
168  comment,
169  renderers = mdRenderers,
170  withDash,
171}) => {
172  const shortText = comment?.shortText?.trim().length ? (
173    <ReactMarkdown renderers={renderers}>{comment.shortText}</ReactMarkdown>
174  ) : null;
175  const text = comment?.text?.trim().length ? (
176    <ReactMarkdown renderers={renderers}>{comment.text}</ReactMarkdown>
177  ) : null;
178
179  const example = comment?.tags?.filter(tag => tag.tag === 'example')[0];
180  const exampleText = example ? (
181    <ReactMarkdown renderers={renderers}>{`__Example:__ ${example.text}`}</ReactMarkdown>
182  ) : null;
183
184  return (
185    <>
186      {withDash && (shortText || text) ? ' - ' : null}
187      {shortText}
188      {text}
189      {exampleText}
190    </>
191  );
192};
193
194export const STYLES_OPTIONAL = css`
195  color: ${theme.text.secondary};
196  font-size: 90%;
197  padding-top: 22px;
198`;
199
200export const STYLES_SECONDARY = css`
201  color: ${theme.text.secondary};
202  font-size: 90%;
203  font-weight: 600;
204`;
205