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