1import { Fragment } from 'react';
2
3import { APIDataType } from '~/components/plugins/api/APIDataType';
4import {
5  PropData,
6  TypeDeclarationContentData,
7  TypeDefinitionData,
8  TypeGeneralData,
9  TypeSignaturesData,
10} from '~/components/plugins/api/APIDataTypes';
11import { APISectionDeprecationNote } from '~/components/plugins/api/APISectionDeprecationNote';
12import { APISectionPlatformTags } from '~/components/plugins/api/APISectionPlatformTags';
13import {
14  resolveTypeName,
15  renderFlags,
16  CommentTextBlock,
17  parseCommentContent,
18  renderTypeOrSignatureType,
19  getCommentOrSignatureComment,
20  getTagData,
21  renderParams,
22  ParamsTableHeadRow,
23  renderDefaultValue,
24  renderIndexSignature,
25  STYLES_APIBOX,
26  getTagNamesList,
27  H3Code,
28  getCommentContent,
29} from '~/components/plugins/api/APISectionUtils';
30import { Cell, Row, Table } from '~/ui/components/Table';
31import { H2, BOLD, P, CODE, MONOSPACE } from '~/ui/components/Text';
32
33export type APISectionTypesProps = {
34  data: TypeGeneralData[];
35};
36
37const defineLiteralType = (types: TypeDefinitionData[]): JSX.Element | null => {
38  const uniqueTypes = Array.from(
39    new Set(types.map((t: TypeDefinitionData) => t.value && typeof t.value))
40  );
41  if (uniqueTypes.length === 1 && uniqueTypes.filter(Boolean).length === 1) {
42    return (
43      <>
44        <CODE>{uniqueTypes[0]}</CODE>
45        {' - '}
46      </>
47    );
48  }
49  return null;
50};
51
52const renderTypeDeclarationTable = (
53  { children, indexSignature, comment }: TypeDeclarationContentData,
54  index?: number
55): JSX.Element => (
56  <Fragment key={`type-declaration-table-${children?.map(child => child.name).join('-')}`}>
57    {index && index > 0 ? <br /> : undefined}
58    <CommentTextBlock comment={comment} />
59    <Table>
60      <ParamsTableHeadRow />
61      <tbody>
62        {children?.map(renderTypePropertyRow)}
63        {indexSignature?.parameters && indexSignature.parameters.map(renderTypePropertyRow)}
64      </tbody>
65    </Table>
66  </Fragment>
67);
68
69const renderTypePropertyRow = ({
70  name,
71  flags,
72  type,
73  comment,
74  defaultValue,
75  signatures,
76  kind,
77}: PropData): JSX.Element => {
78  const defaultTag = getTagData('default', comment);
79  const initValue = parseCommentContent(
80    defaultValue || (defaultTag ? getCommentContent(defaultTag.content) : undefined)
81  );
82  const commentData = getCommentOrSignatureComment(comment, signatures);
83  const hasDeprecationNote = Boolean(getTagData('deprecated', comment));
84  return (
85    <Row key={name}>
86      <Cell fitContent>
87        <BOLD>{name}</BOLD>
88        {renderFlags(flags, initValue)}
89        {kind && renderIndexSignature(kind)}
90      </Cell>
91      <Cell fitContent>{renderTypeOrSignatureType(type, signatures, true)}</Cell>
92      <Cell fitContent>
93        <APISectionDeprecationNote comment={comment} />
94        <CommentTextBlock
95          inlineHeaders
96          comment={commentData}
97          afterContent={renderDefaultValue(initValue)}
98          emptyCommentFallback={hasDeprecationNote ? undefined : '-'}
99        />
100      </Cell>
101    </Row>
102  );
103};
104
105const renderType = ({
106  name,
107  comment,
108  type,
109  typeParameter,
110}: TypeGeneralData): JSX.Element | undefined => {
111  if (type.declaration) {
112    // Object Types
113    return (
114      <div key={`type-definition-${name}`} css={STYLES_APIBOX}>
115        <APISectionDeprecationNote comment={comment} />
116        <APISectionPlatformTags comment={comment} prefix="Only for:" />
117        <H3Code tags={getTagNamesList(comment)}>
118          <MONOSPACE weight="medium">
119            {name}
120            {type.declaration.signatures ? '()' : ''}
121          </MONOSPACE>
122        </H3Code>
123        <CommentTextBlock comment={comment} includePlatforms={false} />
124        {type.declaration.children && renderTypeDeclarationTable(type.declaration)}
125        {type.declaration.signatures
126          ? type.declaration.signatures.map(({ parameters, comment }: TypeSignaturesData) => (
127              <div key={`type-definition-signature-${name}`}>
128                <CommentTextBlock comment={comment} />
129                {parameters && renderParams(parameters)}
130              </div>
131            ))
132          : null}
133      </div>
134    );
135  } else if (type.types && ['union', 'intersection'].includes(type.type)) {
136    const literalTypes = type.types.filter((t: TypeDefinitionData) =>
137      ['literal', 'intrinsic', 'reference', 'tuple'].includes(t.type)
138    );
139    const propTypes = type.types.filter((t: TypeDefinitionData) => t.type === 'reflection');
140    if (propTypes.length) {
141      return (
142        <div key={`prop-type-definition-${name}`} css={STYLES_APIBOX}>
143          <APISectionDeprecationNote comment={comment} />
144          <APISectionPlatformTags comment={comment} prefix="Only for:" />
145          <H3Code tags={getTagNamesList(comment)}>
146            <MONOSPACE weight="medium">{name}</MONOSPACE>
147          </H3Code>
148          <CommentTextBlock comment={comment} includePlatforms={false} />
149          {type.type === 'intersection' ? (
150            <>
151              <P>
152                {type.types
153                  .filter(type => ['reference', 'union', 'intersection'].includes(type.type))
154                  .map(validType => (
155                    <Fragment key={`nested-reference-type-${validType.name}`}>
156                      <CODE>{resolveTypeName(validType)}</CODE>{' '}
157                    </Fragment>
158                  ))}
159                extended by:
160              </P>
161              <br />
162            </>
163          ) : null}
164          {propTypes.map(
165            (propType, index) =>
166              propType.declaration && renderTypeDeclarationTable(propType.declaration, index)
167          )}
168        </div>
169      );
170    } else if (literalTypes.length) {
171      return (
172        <div key={`type-definition-${name}`} css={STYLES_APIBOX}>
173          <APISectionDeprecationNote comment={comment} />
174          <APISectionPlatformTags comment={comment} prefix="Only for:" />
175          <H3Code tags={getTagNamesList(comment)}>
176            <MONOSPACE weight="medium">{name}</MONOSPACE>
177          </H3Code>
178          <CommentTextBlock comment={comment} includePlatforms={false} />
179          <P>
180            {defineLiteralType(literalTypes)}
181            Acceptable values are:{' '}
182            {literalTypes.map((lt, index) => (
183              <span key={`${name}-literal-type-${index}`}>
184                <CODE>{resolveTypeName(lt)}</CODE>
185                {index + 1 !== literalTypes.length ? ', ' : '.'}
186              </span>
187            ))}
188          </P>
189        </div>
190      );
191    }
192  } else if ((type.name === 'Record' && type.typeArguments) || type.type === 'reference') {
193    return (
194      <div key={`record-definition-${name}`} css={STYLES_APIBOX}>
195        <APISectionDeprecationNote comment={comment} />
196        <APISectionPlatformTags comment={comment} prefix="Only for:" />
197        <H3Code tags={getTagNamesList(comment)}>
198          <MONOSPACE weight="medium">{name}</MONOSPACE>
199        </H3Code>
200        <div css={{ display: 'flex', gap: 8, marginBottom: 12 }}>
201          <BOLD>Type: </BOLD>
202          <APIDataType typeDefinition={type} />
203        </div>
204        <CommentTextBlock comment={comment} includePlatforms={false} />
205      </div>
206    );
207  } else if (type.type === 'intrinsic') {
208    return (
209      <div key={`generic-type-definition-${name}`} css={STYLES_APIBOX}>
210        <APISectionDeprecationNote comment={comment} />
211        <APISectionPlatformTags comment={comment} prefix="Only for:" />
212        <H3Code tags={getTagNamesList(comment)}>
213          <MONOSPACE weight="medium">{name}</MONOSPACE>
214        </H3Code>
215        <CommentTextBlock comment={comment} includePlatforms={false} />
216        <P>
217          <BOLD>Type: </BOLD>
218          <CODE>{type.name}</CODE>
219        </P>
220      </div>
221    );
222  } else if (type.type === 'conditional' && type.checkType) {
223    return (
224      <div key={`conditional-type-definition-${name}`} css={STYLES_APIBOX}>
225        <APISectionDeprecationNote comment={comment} />
226        <APISectionPlatformTags comment={comment} prefix="Only for:" />
227        <H3Code tags={getTagNamesList(comment)}>
228          <MONOSPACE weight="medium">
229            {name}&lt;{type.checkType.name}&gt;
230          </MONOSPACE>
231        </H3Code>
232        <CommentTextBlock comment={comment} includePlatforms={false} />
233        <P>
234          <BOLD>Generic: </BOLD>
235          <CODE>
236            {type.checkType.name}
237            {typeParameter && <> extends {resolveTypeName(typeParameter[0].type)}</>}
238          </CODE>
239        </P>
240        <P>
241          <BOLD>Type: </BOLD>
242          <CODE>
243            {type.checkType.name}
244            {typeParameter && <> extends {type.extendsType && resolveTypeName(type.extendsType)}</>}
245            {' ? '}
246            {type.trueType && resolveTypeName(type.trueType)}
247            {' : '}
248            {type.falseType && resolveTypeName(type.falseType)}
249          </CODE>
250        </P>
251      </div>
252    );
253  }
254  return undefined;
255};
256
257const APISectionTypes = ({ data }: APISectionTypesProps) =>
258  data?.length ? (
259    <>
260      <H2 key="types-header">Types</H2>
261      {data.map(renderType)}
262    </>
263  ) : null;
264
265export default APISectionTypes;
266