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