xref: /expo/docs/components/plugins/APISection.tsx (revision ca1df09a)
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].type.types && signatures[0].type.types.map(t => t.name).includes('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(data, TypeDocKind.Enum, entry => entry.name !== 'default');
116    const interfaces = filterDataByKind(data, TypeDocKind.Interface);
117    const constants = filterDataByKind(data, TypeDocKind.Variable, entry => isConstant(entry));
118
119    const components = filterDataByKind(
120      data,
121      [TypeDocKind.Variable, TypeDocKind.Class, TypeDocKind.Function],
122      entry => isComponent(entry)
123    );
124    const componentsPropNames = components.map(
125      ({ name, children }) => `${getComponentName(name, children)}Props`
126    );
127    const componentsProps = filterDataByKind(props, TypeDocKind.TypeAlias, entry =>
128      componentsPropNames.includes(entry.name)
129    );
130
131    const namespaces = filterDataByKind(data, TypeDocKind.Namespace);
132
133    const classes = filterDataByKind(
134      data,
135      TypeDocKind.Class,
136      entry => !isComponent(entry) && entry.name !== 'default'
137    );
138
139    const componentsChildren = components
140      .map((cls: ClassDefinitionData) =>
141        cls.children?.filter(
142          child =>
143            (child?.kind === TypeDocKind.Method || child?.kind === TypeDocKind.Property) &&
144            child?.flags?.isExternal !== true &&
145            !child.inheritedFrom &&
146            child.name !== 'render' &&
147            // note(simek): hide unannotated "private" methods
148            !child.name.startsWith('_')
149        )
150      )
151      .flat();
152
153    const methodsNames = methods.map(method => method.name);
154    const staticMethods = componentsChildren.filter(
155      // note(simek): hide duplicate exports from class components
156      method =>
157        method?.kind === TypeDocKind.Method &&
158        method?.flags?.isStatic === true &&
159        !methodsNames.includes(method.name) &&
160        !isHook(method as GeneratedData)
161    );
162    const componentMethods = componentsChildren
163      .filter(
164        method =>
165          method?.kind === TypeDocKind.Method &&
166          method?.flags?.isStatic !== true &&
167          !method?.overwrites
168      )
169      .filter(Boolean);
170
171    const hooks = filterDataByKind(
172      [...data, ...componentsChildren].filter(Boolean),
173      [TypeDocKind.Function, TypeDocKind.Property],
174      isHook
175    );
176
177    return (
178      <>
179        <APISectionComponents data={components} componentsProps={componentsProps} />
180        <APISectionMethods data={staticMethods} header="Static Methods" />
181        <APISectionMethods data={componentMethods} header="Component Methods" />
182        <APISectionConstants data={constants} apiName={apiName} />
183        <APISectionMethods data={hooks} header="Hooks" />
184        <APISectionClasses data={classes} />
185        {props && !componentsProps.length ? (
186          <APISectionProps data={props} defaultProps={defaultProps} />
187        ) : null}
188        <APISectionMethods data={methods} apiName={apiName} />
189        <APISectionMethods
190          data={eventSubscriptions}
191          apiName={apiName}
192          header="Event Subscriptions"
193        />
194        <APISectionNamespaces data={namespaces} />
195        <APISectionInterfaces data={interfaces} />
196        <APISectionTypes data={types} />
197        <APISectionEnums data={enums} />
198      </>
199    );
200  } catch {
201    return <P>No API data file found, sorry!</P>;
202  }
203};
204
205const APISection = ({
206  packageName,
207  apiName,
208  forceVersion,
209  strictTypes = false,
210  testRequire = undefined,
211}: Props) => {
212  const { version } = usePageApiVersion();
213  const resolvedVersion =
214    forceVersion ||
215    (version === 'unversioned' ? version : version === 'latest' ? LATEST_VERSION : version);
216  return renderAPI(packageName, resolvedVersion, apiName, strictTypes, testRequire);
217};
218
219export default APISection;
220