xref: /expo/docs/components/plugins/APISection.tsx (revision bb606df4)
1import React from 'react';
2
3import { P } from '~/components/base/paragraph';
4import { ClassDefinitionData, GeneratedData } from '~/components/plugins/api/APIDataTypes';
5import APISectionClasses from '~/components/plugins/api/APISectionClasses';
6import APISectionComponents from '~/components/plugins/api/APISectionComponents';
7import APISectionConstants from '~/components/plugins/api/APISectionConstants';
8import APISectionEnums from '~/components/plugins/api/APISectionEnums';
9import APISectionInterfaces from '~/components/plugins/api/APISectionInterfaces';
10import APISectionMethods from '~/components/plugins/api/APISectionMethods';
11import APISectionNamespaces from '~/components/plugins/api/APISectionNamespaces';
12import APISectionProps from '~/components/plugins/api/APISectionProps';
13import APISectionTypes from '~/components/plugins/api/APISectionTypes';
14import { getComponentName, TypeDocKind } from '~/components/plugins/api/APISectionUtils';
15import { usePageApiVersion } from '~/providers/page-api-version';
16import versions from '~/public/static/constants/versions.json';
17
18const { LATEST_VERSION } = versions;
19
20type Props = {
21  packageName: string;
22  apiName?: string;
23  forceVersion?: string;
24  strictTypes?: boolean;
25  testRequire?: any;
26};
27
28const filterDataByKind = (
29  entries: GeneratedData[] = [],
30  kind: TypeDocKind | TypeDocKind[],
31  additionalCondition: (entry: GeneratedData) => boolean = () => true
32) =>
33  entries.filter(
34    (entry: GeneratedData) =>
35      (Array.isArray(kind) ? kind.includes(entry.kind) : entry.kind === kind) &&
36      additionalCondition(entry)
37  );
38
39const isHook = ({ name }: GeneratedData) =>
40  name.startsWith('use') &&
41  // note(simek): hardcode this exception until the method will be renamed
42  name !== 'useSystemBrightnessAsync';
43
44const isListener = ({ name }: GeneratedData) =>
45  name.endsWith('Listener') || name.endsWith('Listeners');
46
47const isProp = ({ name }: GeneratedData) => name.includes('Props') && name !== 'ErrorRecoveryProps';
48
49const isComponent = ({ type, extendedTypes, signatures }: GeneratedData) => {
50  if (type?.name && ['React.FC', 'ForwardRefExoticComponent'].includes(type?.name)) {
51    return true;
52  } else if (extendedTypes && extendedTypes.length) {
53    return extendedTypes[0].name === 'Component';
54  } else if (signatures && signatures.length) {
55    if (
56      signatures[0].type.name === 'Element' ||
57      (signatures[0].parameters && signatures[0].parameters[0].name === 'props')
58    ) {
59      return true;
60    }
61  }
62  return false;
63};
64
65const isConstant = ({ name, type }: GeneratedData) =>
66  !['default', 'Constants', 'EventEmitter'].includes(name) &&
67  !(type?.name && ['React.FC', 'ForwardRefExoticComponent'].includes(type?.name));
68
69const renderAPI = (
70  packageName: string,
71  version: string = 'unversioned',
72  apiName?: string,
73  strictTypes: boolean = false,
74  testRequire: any = undefined
75): JSX.Element => {
76  try {
77    const { children: data } = testRequire
78      ? testRequire(`~/public/static/data/${version}/${packageName}.json`)
79      : require(`~/public/static/data/${version}/${packageName}.json`);
80
81    const methods = filterDataByKind(
82      data,
83      TypeDocKind.Function,
84      entry => !isListener(entry) && !isHook(entry) && !isComponent(entry)
85    );
86    const eventSubscriptions = filterDataByKind(data, TypeDocKind.Function, isListener);
87
88    const types = filterDataByKind(
89      data,
90      TypeDocKind.TypeAlias,
91      entry =>
92        !isProp(entry) &&
93        !!(
94          entry.type.declaration ||
95          entry.type.types ||
96          entry.type.type ||
97          entry.type.typeArguments
98        ) &&
99        (strictTypes && apiName ? entry.name.startsWith(apiName) : true)
100    );
101
102    const props = filterDataByKind(
103      data,
104      TypeDocKind.TypeAlias,
105      entry => isProp(entry) && !!(entry.type.types || entry.type.declaration?.children)
106    );
107    const defaultProps = filterDataByKind(
108      data
109        .filter((entry: GeneratedData) => entry.kind === TypeDocKind.Class)
110        .map((entry: GeneratedData) => entry.children)
111        .flat(),
112      TypeDocKind.Property,
113      entry => entry.name === 'defaultProps'
114    )[0];
115
116    const enums = filterDataByKind(data, TypeDocKind.Enum, entry => entry.name !== 'default');
117    const interfaces = filterDataByKind(data, TypeDocKind.Interface);
118    const constants = filterDataByKind(data, TypeDocKind.Variable, entry => isConstant(entry));
119
120    const components = filterDataByKind(
121      data,
122      [TypeDocKind.Variable, TypeDocKind.Class, TypeDocKind.Function],
123      entry => isComponent(entry)
124    );
125    const componentsPropNames = components.map(
126      ({ name, children }) => `${getComponentName(name, children)}Props`
127    );
128    const componentsProps = filterDataByKind(props, TypeDocKind.TypeAlias, entry =>
129      componentsPropNames.includes(entry.name)
130    );
131
132    const namespaces = filterDataByKind(data, TypeDocKind.Namespace);
133
134    const classes = filterDataByKind(
135      data,
136      TypeDocKind.Class,
137      entry => !isComponent(entry) && entry.name !== 'default'
138    );
139
140    const componentsChildren = components
141      .map((cls: ClassDefinitionData) =>
142        cls.children?.filter(
143          child =>
144            (child?.kind === TypeDocKind.Method || child?.kind === TypeDocKind.Property) &&
145            child?.flags?.isExternal !== true &&
146            !child.inheritedFrom &&
147            child.name !== 'render' &&
148            // note(simek): hide unannotated "private" methods
149            !child.name.startsWith('_')
150        )
151      )
152      .flat();
153
154    const methodsNames = methods.map(method => method.name);
155    const staticMethods = componentsChildren.filter(
156      // note(simek): hide duplicate exports from class components
157      method =>
158        method?.kind === TypeDocKind.Method &&
159        method?.flags?.isStatic === true &&
160        !methodsNames.includes(method.name) &&
161        !isHook(method as GeneratedData)
162    );
163    const componentMethods = componentsChildren
164      .filter(
165        method =>
166          method?.kind === TypeDocKind.Method &&
167          method?.flags?.isStatic !== true &&
168          !method?.overwrites
169      )
170      .filter(Boolean);
171
172    const hooks = filterDataByKind(
173      [...data, ...componentsChildren].filter(Boolean),
174      [TypeDocKind.Function, TypeDocKind.Property],
175      isHook
176    );
177
178    return (
179      <>
180        <APISectionComponents data={components} componentsProps={componentsProps} />
181        <APISectionMethods data={staticMethods} header="Static Methods" />
182        <APISectionMethods data={componentMethods} header="Component Methods" />
183        <APISectionConstants data={constants} apiName={apiName} />
184        <APISectionMethods data={hooks} header="Hooks" />
185        <APISectionClasses data={classes} />
186        {props && !componentsProps.length ? (
187          <APISectionProps data={props} defaultProps={defaultProps} />
188        ) : null}
189        <APISectionMethods data={methods} apiName={apiName} />
190        <APISectionMethods
191          data={eventSubscriptions}
192          apiName={apiName}
193          header="Event Subscriptions"
194        />
195        <APISectionNamespaces data={namespaces} />
196        <APISectionInterfaces data={interfaces} />
197        <APISectionTypes data={types} />
198        <APISectionEnums data={enums} />
199      </>
200    );
201  } catch {
202    return <P>No API data file found, sorry!</P>;
203  }
204};
205
206const APISection = ({
207  packageName,
208  apiName,
209  forceVersion,
210  strictTypes = false,
211  testRequire = undefined,
212}: Props) => {
213  const { version } = usePageApiVersion();
214  const resolvedVersion =
215    forceVersion ||
216    (version === 'unversioned' ? version : version === 'latest' ? LATEST_VERSION : version);
217  return renderAPI(packageName, resolvedVersion, apiName, strictTypes, testRequire);
218};
219
220export default APISection;
221