xref: /expo/docs/components/plugins/APISection.tsx (revision 7684899a)
1e3060685SBartosz Kaszubowskiimport { ClassDefinitionData, GeneratedData } from '~/components/plugins/api/APIDataTypes';
22c8d37c0SBartosz Kaszubowskiimport APISectionClasses from '~/components/plugins/api/APISectionClasses';
31ef472c3SBartosz Kaszubowskiimport APISectionComponents from '~/components/plugins/api/APISectionComponents';
4299f02f2SBartosz Kaszubowskiimport APISectionConstants from '~/components/plugins/api/APISectionConstants';
5299f02f2SBartosz Kaszubowskiimport APISectionEnums from '~/components/plugins/api/APISectionEnums';
6299f02f2SBartosz Kaszubowskiimport APISectionInterfaces from '~/components/plugins/api/APISectionInterfaces';
7299f02f2SBartosz Kaszubowskiimport APISectionMethods from '~/components/plugins/api/APISectionMethods';
86b7802baSBartosz Kaszubowskiimport APISectionNamespaces from '~/components/plugins/api/APISectionNamespaces';
9299f02f2SBartosz Kaszubowskiimport APISectionProps from '~/components/plugins/api/APISectionProps';
10299f02f2SBartosz Kaszubowskiimport APISectionTypes from '~/components/plugins/api/APISectionTypes';
11*07ffa84cSBartosz Kaszubowskiimport {
12*07ffa84cSBartosz Kaszubowski  getCommentContent,
13*07ffa84cSBartosz Kaszubowski  getComponentName,
14*07ffa84cSBartosz Kaszubowski  TypeDocKind,
15*07ffa84cSBartosz Kaszubowski} from '~/components/plugins/api/APISectionUtils';
16e54a3f4bSCedric van Puttenimport { usePageApiVersion } from '~/providers/page-api-version';
173f609562SBartosz Kaszubowskiimport versions from '~/public/static/constants/versions.json';
1812abeb84SBartosz Kaszubowskiimport { P } from '~/ui/components/Text';
19299f02f2SBartosz Kaszubowski
203f609562SBartosz Kaszubowskiconst { LATEST_VERSION } = versions;
21299f02f2SBartosz Kaszubowski
22299f02f2SBartosz Kaszubowskitype Props = {
23299f02f2SBartosz Kaszubowski  packageName: string;
24299f02f2SBartosz Kaszubowski  apiName?: string;
258de46705SBartosz Kaszubowski  forceVersion?: string;
269f72d43bSBartosz Kaszubowski  strictTypes?: boolean;
273f609562SBartosz Kaszubowski  testRequire?: any;
28*07ffa84cSBartosz Kaszubowski  headersMapping?: Record<string, string>;
29299f02f2SBartosz Kaszubowski};
30299f02f2SBartosz Kaszubowski
31299f02f2SBartosz Kaszubowskiconst filterDataByKind = (
32c8c9d6e8SBartosz Kaszubowski  entries: GeneratedData[] = [],
33c8c9d6e8SBartosz Kaszubowski  kind: TypeDocKind | TypeDocKind[],
34299f02f2SBartosz Kaszubowski  additionalCondition: (entry: GeneratedData) => boolean = () => true
35299f02f2SBartosz Kaszubowski) =>
36c8c9d6e8SBartosz Kaszubowski  entries.filter(
37c8c9d6e8SBartosz Kaszubowski    (entry: GeneratedData) =>
38c8c9d6e8SBartosz Kaszubowski      (Array.isArray(kind) ? kind.includes(entry.kind) : entry.kind === kind) &&
39c8c9d6e8SBartosz Kaszubowski      additionalCondition(entry)
40c8c9d6e8SBartosz Kaszubowski  );
41299f02f2SBartosz Kaszubowski
42a2ea8bc3SBartosz Kaszubowskiconst isHook = ({ name }: GeneratedData) =>
43a2ea8bc3SBartosz Kaszubowski  name.startsWith('use') &&
44a2ea8bc3SBartosz Kaszubowski  // note(simek): hardcode this exception until the method will be renamed
45a2ea8bc3SBartosz Kaszubowski  name !== 'useSystemBrightnessAsync';
46a2ea8bc3SBartosz Kaszubowski
4704eb1228SBartosz Kaszubowskiconst isListener = ({ name }: GeneratedData) =>
4804eb1228SBartosz Kaszubowski  name.endsWith('Listener') || name.endsWith('Listeners');
49f128dd42SBartosz Kaszubowski
501db397d9SBartosz Kaszubowskiconst isProp = ({ name }: GeneratedData) => name.includes('Props') && name !== 'ErrorRecoveryProps';
511db397d9SBartosz Kaszubowski
52e939dadbSBartosz Kaszubowskiconst isComponent = ({ type, extendedTypes, signatures }: GeneratedData) => {
53e939dadbSBartosz Kaszubowski  if (type?.name && ['React.FC', 'ForwardRefExoticComponent'].includes(type?.name)) {
54e939dadbSBartosz Kaszubowski    return true;
55e939dadbSBartosz Kaszubowski  } else if (extendedTypes && extendedTypes.length) {
566d4adf53STomasz Sapeta    return extendedTypes[0].name === 'Component' || extendedTypes[0].name === 'PureComponent';
57e939dadbSBartosz Kaszubowski  } else if (signatures && signatures.length) {
58e939dadbSBartosz Kaszubowski    if (
59e939dadbSBartosz Kaszubowski      signatures[0].type.name === 'Element' ||
605990cc31SBartosz Kaszubowski      (signatures[0].type.types && signatures[0].type.types.map(t => t.name).includes('Element')) ||
61144e76edSBartosz Kaszubowski      (signatures[0].parameters && signatures[0].parameters[0].name === 'props')
62e939dadbSBartosz Kaszubowski    ) {
63e939dadbSBartosz Kaszubowski      return true;
64e939dadbSBartosz Kaszubowski    }
65e939dadbSBartosz Kaszubowski  }
66e939dadbSBartosz Kaszubowski  return false;
67e939dadbSBartosz Kaszubowski};
68f31f564bSBartosz Kaszubowski
69d0262524SBartosz Kaszubowskiconst isConstant = ({ name, type }: GeneratedData) =>
70e3060685SBartosz Kaszubowski  !['default', 'Constants', 'EventEmitter'].includes(name) &&
71ce122823SBartłomiej Bukowski  !(type?.name && ['React.FC', 'ForwardRefExoticComponent'].includes(type?.name));
72f31f564bSBartosz Kaszubowski
73*07ffa84cSBartosz Kaszubowskiconst hasCategoryHeader = ({ signatures }: GeneratedData): boolean =>
74*07ffa84cSBartosz Kaszubowski  (signatures &&
75*07ffa84cSBartosz Kaszubowski    signatures[0].comment?.blockTags &&
76*07ffa84cSBartosz Kaszubowski    signatures[0].comment.blockTags.length > 0 &&
77*07ffa84cSBartosz Kaszubowski    signatures[0].comment.blockTags.filter(tag => tag?.tag === '@header').length > 0) ??
78*07ffa84cSBartosz Kaszubowski  false;
79*07ffa84cSBartosz Kaszubowski
80*07ffa84cSBartosz Kaszubowskiconst groupByHeader = (entries: GeneratedData[]) => {
81*07ffa84cSBartosz Kaszubowski  return entries.reduce((group: Record<string, GeneratedData[]>, entry) => {
82*07ffa84cSBartosz Kaszubowski    const signature = entry.signatures[0];
83*07ffa84cSBartosz Kaszubowski    const header = getCommentContent(
84*07ffa84cSBartosz Kaszubowski      signature.comment?.blockTags?.filter(tag => tag.tag === '@header')[0].content ?? []
85*07ffa84cSBartosz Kaszubowski    );
86*07ffa84cSBartosz Kaszubowski    if (header) {
87*07ffa84cSBartosz Kaszubowski      group[header] = group[header] ?? [];
88*07ffa84cSBartosz Kaszubowski      group[header].push(entry);
89*07ffa84cSBartosz Kaszubowski    }
90*07ffa84cSBartosz Kaszubowski    return group;
91*07ffa84cSBartosz Kaszubowski  }, {});
92*07ffa84cSBartosz Kaszubowski};
93*07ffa84cSBartosz Kaszubowski
94299f02f2SBartosz Kaszubowskiconst renderAPI = (
95299f02f2SBartosz Kaszubowski  packageName: string,
96299f02f2SBartosz Kaszubowski  version: string = 'unversioned',
978de46705SBartosz Kaszubowski  apiName?: string,
989f72d43bSBartosz Kaszubowski  strictTypes: boolean = false,
99*07ffa84cSBartosz Kaszubowski  testRequire: any = undefined,
100*07ffa84cSBartosz Kaszubowski  headersMapping: Record<string, string> = {}
101299f02f2SBartosz Kaszubowski): JSX.Element => {
102299f02f2SBartosz Kaszubowski  try {
1033f609562SBartosz Kaszubowski    const { children: data } = testRequire
1043f609562SBartosz Kaszubowski      ? testRequire(`~/public/static/data/${version}/${packageName}.json`)
1058de46705SBartosz Kaszubowski      : require(`~/public/static/data/${version}/${packageName}.json`);
106299f02f2SBartosz Kaszubowski
1071b3dce65SBartosz Kaszubowski    const methods = filterDataByKind(
1081b3dce65SBartosz Kaszubowski      data,
1091b3dce65SBartosz Kaszubowski      TypeDocKind.Function,
110*07ffa84cSBartosz Kaszubowski      entry =>
111*07ffa84cSBartosz Kaszubowski        !isListener(entry) && !isHook(entry) && !isComponent(entry) && !hasCategoryHeader(entry)
1121b3dce65SBartosz Kaszubowski    );
113*07ffa84cSBartosz Kaszubowski    const eventSubscriptions = filterDataByKind(
114*07ffa84cSBartosz Kaszubowski      data,
115*07ffa84cSBartosz Kaszubowski      TypeDocKind.Function,
116*07ffa84cSBartosz Kaszubowski      entry => isListener(entry) && !hasCategoryHeader(entry)
117*07ffa84cSBartosz Kaszubowski    );
118*07ffa84cSBartosz Kaszubowski
119*07ffa84cSBartosz Kaszubowski    const categorizedMethods = groupByHeader(
120*07ffa84cSBartosz Kaszubowski      filterDataByKind(
121*07ffa84cSBartosz Kaszubowski        data,
122*07ffa84cSBartosz Kaszubowski        TypeDocKind.Function,
123*07ffa84cSBartosz Kaszubowski        entry => !isComponent(entry) && hasCategoryHeader(entry)
124*07ffa84cSBartosz Kaszubowski      )
125*07ffa84cSBartosz Kaszubowski    );
126*07ffa84cSBartosz Kaszubowski    const hasCategorizedMethods = Object.keys(categorizedMethods).length > 0;
127*07ffa84cSBartosz Kaszubowski    const hasHeadersMapping = Object.keys(headersMapping).length;
128f128dd42SBartosz Kaszubowski
129299f02f2SBartosz Kaszubowski    const types = filterDataByKind(
130299f02f2SBartosz Kaszubowski      data,
131299f02f2SBartosz Kaszubowski      TypeDocKind.TypeAlias,
1328e38d640SBartosz Kaszubowski      entry =>
1331db397d9SBartosz Kaszubowski        !isProp(entry) &&
1348e38d640SBartosz Kaszubowski        !!(
1358e38d640SBartosz Kaszubowski          entry.type.declaration ||
1368e38d640SBartosz Kaszubowski          entry.type.types ||
1378e38d640SBartosz Kaszubowski          entry.type.type ||
1388e38d640SBartosz Kaszubowski          entry.type.typeArguments
1399f72d43bSBartosz Kaszubowski        ) &&
1409f72d43bSBartosz Kaszubowski        (strictTypes && apiName ? entry.name.startsWith(apiName) : true)
141299f02f2SBartosz Kaszubowski    );
142a404f439SBartosz Kaszubowski
14307337117SBartosz Kaszubowski    const props = filterDataByKind(
14407337117SBartosz Kaszubowski      data,
145c0b543d7SBartosz Kaszubowski      [TypeDocKind.TypeAlias, TypeDocKind.Interface],
146c0b543d7SBartosz Kaszubowski      entry =>
147c0b543d7SBartosz Kaszubowski        isProp(entry) &&
148c0b543d7SBartosz Kaszubowski        (entry.kind === TypeDocKind.TypeAlias
149c0b543d7SBartosz Kaszubowski          ? !!(entry.type.types || entry.type.declaration?.children)
150c0b543d7SBartosz Kaszubowski          : true)
151299f02f2SBartosz Kaszubowski    );
152299f02f2SBartosz Kaszubowski    const defaultProps = filterDataByKind(
153299f02f2SBartosz Kaszubowski      data
154299f02f2SBartosz Kaszubowski        .filter((entry: GeneratedData) => entry.kind === TypeDocKind.Class)
155299f02f2SBartosz Kaszubowski        .map((entry: GeneratedData) => entry.children)
156299f02f2SBartosz Kaszubowski        .flat(),
157299f02f2SBartosz Kaszubowski      TypeDocKind.Property,
158299f02f2SBartosz Kaszubowski      entry => entry.name === 'defaultProps'
159299f02f2SBartosz Kaszubowski    )[0];
1601ef472c3SBartosz Kaszubowski
1616b7802baSBartosz Kaszubowski    const enums = filterDataByKind(data, TypeDocKind.Enum, entry => entry.name !== 'default');
162c0b543d7SBartosz Kaszubowski    const interfaces = filterDataByKind(
163c0b543d7SBartosz Kaszubowski      data,
164c0b543d7SBartosz Kaszubowski      TypeDocKind.Interface,
165c0b543d7SBartosz Kaszubowski      entry => !entry.name.includes('Props')
166c0b543d7SBartosz Kaszubowski    );
167f31f564bSBartosz Kaszubowski    const constants = filterDataByKind(data, TypeDocKind.Variable, entry => isConstant(entry));
1681ef472c3SBartosz Kaszubowski
169144e76edSBartosz Kaszubowski    const components = filterDataByKind(
170144e76edSBartosz Kaszubowski      data,
171144e76edSBartosz Kaszubowski      [TypeDocKind.Variable, TypeDocKind.Class, TypeDocKind.Function],
172144e76edSBartosz Kaszubowski      entry => isComponent(entry)
1731ef472c3SBartosz Kaszubowski    );
1749f72d43bSBartosz Kaszubowski    const componentsPropNames = components.map(
1759f72d43bSBartosz Kaszubowski      ({ name, children }) => `${getComponentName(name, children)}Props`
1769f72d43bSBartosz Kaszubowski    );
177c0b543d7SBartosz Kaszubowski    const componentsProps = filterDataByKind(
178c0b543d7SBartosz Kaszubowski      props,
179c0b543d7SBartosz Kaszubowski      [TypeDocKind.TypeAlias, TypeDocKind.Interface],
180c0b543d7SBartosz Kaszubowski      entry => componentsPropNames.includes(entry.name)
1811a91cb86SBartosz Kaszubowski    );
182299f02f2SBartosz Kaszubowski
1836b7802baSBartosz Kaszubowski    const namespaces = filterDataByKind(data, TypeDocKind.Namespace);
1846b7802baSBartosz Kaszubowski
18535e5274fSBartosz Kaszubowski    const classes = filterDataByKind(
18635e5274fSBartosz Kaszubowski      data,
18735e5274fSBartosz Kaszubowski      TypeDocKind.Class,
18835e5274fSBartosz Kaszubowski      entry => !isComponent(entry) && entry.name !== 'default'
18935e5274fSBartosz Kaszubowski    );
1902c8d37c0SBartosz Kaszubowski
191e3060685SBartosz Kaszubowski    const componentsChildren = components
192e3060685SBartosz Kaszubowski      .map((cls: ClassDefinitionData) =>
193e3060685SBartosz Kaszubowski        cls.children?.filter(
194e3060685SBartosz Kaszubowski          child =>
195dd9570b9SBartosz Kaszubowski            (child?.kind === TypeDocKind.Method || child?.kind === TypeDocKind.Property) &&
196dd9570b9SBartosz Kaszubowski            !child.inheritedFrom &&
197e3060685SBartosz Kaszubowski            child.name !== 'render' &&
198e3060685SBartosz Kaszubowski            // note(simek): hide unannotated "private" methods
199e3060685SBartosz Kaszubowski            !child.name.startsWith('_')
200e3060685SBartosz Kaszubowski        )
201e3060685SBartosz Kaszubowski      )
202e3060685SBartosz Kaszubowski      .flat();
203e3060685SBartosz Kaszubowski
204e3060685SBartosz Kaszubowski    const methodsNames = methods.map(method => method.name);
205e3060685SBartosz Kaszubowski    const staticMethods = componentsChildren.filter(
206dd9570b9SBartosz Kaszubowski      // note(simek): hide duplicate exports from class components
207dd9570b9SBartosz Kaszubowski      method =>
208dd9570b9SBartosz Kaszubowski        method?.kind === TypeDocKind.Method &&
209dd9570b9SBartosz Kaszubowski        method?.flags?.isStatic === true &&
210dd9570b9SBartosz Kaszubowski        !methodsNames.includes(method.name) &&
211dd9570b9SBartosz Kaszubowski        !isHook(method as GeneratedData)
212e3060685SBartosz Kaszubowski    );
213e3060685SBartosz Kaszubowski    const componentMethods = componentsChildren
214dd9570b9SBartosz Kaszubowski      .filter(
215dd9570b9SBartosz Kaszubowski        method =>
216dd9570b9SBartosz Kaszubowski          method?.kind === TypeDocKind.Method &&
217dd9570b9SBartosz Kaszubowski          method?.flags?.isStatic !== true &&
218dd9570b9SBartosz Kaszubowski          !method?.overwrites
219dd9570b9SBartosz Kaszubowski      )
220e3060685SBartosz Kaszubowski      .filter(Boolean);
221e3060685SBartosz Kaszubowski
222dd9570b9SBartosz Kaszubowski    const hooks = filterDataByKind(
223dd9570b9SBartosz Kaszubowski      [...data, ...componentsChildren].filter(Boolean),
224dd9570b9SBartosz Kaszubowski      [TypeDocKind.Function, TypeDocKind.Property],
225*07ffa84cSBartosz Kaszubowski      entry => isHook(entry) && !hasCategoryHeader(entry)
226dd9570b9SBartosz Kaszubowski    );
227dd9570b9SBartosz Kaszubowski
228299f02f2SBartosz Kaszubowski    return (
229299f02f2SBartosz Kaszubowski      <>
230*07ffa84cSBartosz Kaszubowski        {hasCategorizedMethods &&
231*07ffa84cSBartosz Kaszubowski          (hasHeadersMapping
232*07ffa84cSBartosz Kaszubowski            ? Object.entries(headersMapping).map(([key, header], index) => (
233*07ffa84cSBartosz Kaszubowski                <APISectionMethods
234*07ffa84cSBartosz Kaszubowski                  data={categorizedMethods[key]}
235*07ffa84cSBartosz Kaszubowski                  header={header}
236*07ffa84cSBartosz Kaszubowski                  key={`${header}-${index}`}
237*07ffa84cSBartosz Kaszubowski                />
238*07ffa84cSBartosz Kaszubowski              ))
239*07ffa84cSBartosz Kaszubowski            : Object.entries(categorizedMethods).map(([key, data], index) => (
240*07ffa84cSBartosz Kaszubowski                <APISectionMethods data={data} header={key} key={`${key}-${index}`} />
241*07ffa84cSBartosz Kaszubowski              )))}
2421ef472c3SBartosz Kaszubowski        <APISectionComponents data={components} componentsProps={componentsProps} />
243e3060685SBartosz Kaszubowski        <APISectionMethods data={staticMethods} header="Static Methods" />
244e3060685SBartosz Kaszubowski        <APISectionMethods data={componentMethods} header="Component Methods" />
245299f02f2SBartosz Kaszubowski        <APISectionConstants data={constants} apiName={apiName} />
246f128dd42SBartosz Kaszubowski        <APISectionMethods data={hooks} header="Hooks" />
247d8d79196SBartosz Kaszubowski        <APISectionClasses data={classes} />
248144e76edSBartosz Kaszubowski        {props && !componentsProps.length ? (
249144e76edSBartosz Kaszubowski          <APISectionProps data={props} defaultProps={defaultProps} />
250144e76edSBartosz Kaszubowski        ) : null}
251299f02f2SBartosz Kaszubowski        <APISectionMethods data={methods} apiName={apiName} />
2521b3dce65SBartosz Kaszubowski        <APISectionMethods
2531b3dce65SBartosz Kaszubowski          data={eventSubscriptions}
2541b3dce65SBartosz Kaszubowski          apiName={apiName}
2551b3dce65SBartosz Kaszubowski          header="Event Subscriptions"
2561b3dce65SBartosz Kaszubowski        />
2576b7802baSBartosz Kaszubowski        <APISectionNamespaces data={namespaces} />
258299f02f2SBartosz Kaszubowski        <APISectionInterfaces data={interfaces} />
2594c73bbf6SBartosz Kaszubowski        <APISectionTypes data={types} />
260299f02f2SBartosz Kaszubowski        <APISectionEnums data={enums} />
261299f02f2SBartosz Kaszubowski      </>
262299f02f2SBartosz Kaszubowski    );
2637274e17dSBartosz Kaszubowski  } catch {
264299f02f2SBartosz Kaszubowski    return <P>No API data file found, sorry!</P>;
265299f02f2SBartosz Kaszubowski  }
266299f02f2SBartosz Kaszubowski};
267299f02f2SBartosz Kaszubowski
2683f609562SBartosz Kaszubowskiconst APISection = ({
2693f609562SBartosz Kaszubowski  packageName,
2703f609562SBartosz Kaszubowski  apiName,
2713f609562SBartosz Kaszubowski  forceVersion,
2723f609562SBartosz Kaszubowski  strictTypes = false,
2733f609562SBartosz Kaszubowski  testRequire = undefined,
274*07ffa84cSBartosz Kaszubowski  headersMapping = {},
2753f609562SBartosz Kaszubowski}: Props) => {
276e54a3f4bSCedric van Putten  const { version } = usePageApiVersion();
277299f02f2SBartosz Kaszubowski  const resolvedVersion =
2788de46705SBartosz Kaszubowski    forceVersion ||
2798de46705SBartosz Kaszubowski    (version === 'unversioned' ? version : version === 'latest' ? LATEST_VERSION : version);
280*07ffa84cSBartosz Kaszubowski  return renderAPI(packageName, resolvedVersion, apiName, strictTypes, testRequire, headersMapping);
281299f02f2SBartosz Kaszubowski};
282299f02f2SBartosz Kaszubowski
283299f02f2SBartosz Kaszubowskiexport default APISection;
284