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 { 12 getCommentContent, 13 getComponentName, 14 TypeDocKind, 15} from '~/components/plugins/api/APISectionUtils'; 16import { usePageApiVersion } from '~/providers/page-api-version'; 17import versions from '~/public/static/constants/versions.json'; 18import { P } from '~/ui/components/Text'; 19 20const { LATEST_VERSION } = versions; 21 22type Props = { 23 packageName: string; 24 apiName?: string; 25 forceVersion?: string; 26 strictTypes?: boolean; 27 testRequire?: any; 28 headersMapping?: Record<string, string>; 29}; 30 31const filterDataByKind = ( 32 entries: GeneratedData[] = [], 33 kind: TypeDocKind | TypeDocKind[], 34 additionalCondition: (entry: GeneratedData) => boolean = () => true 35) => 36 entries.filter( 37 (entry: GeneratedData) => 38 (Array.isArray(kind) ? kind.includes(entry.kind) : entry.kind === kind) && 39 additionalCondition(entry) 40 ); 41 42const isHook = ({ name }: GeneratedData) => 43 name.startsWith('use') && 44 // note(simek): hardcode this exception until the method will be renamed 45 name !== 'useSystemBrightnessAsync'; 46 47const isListener = ({ name }: GeneratedData) => 48 name.endsWith('Listener') || name.endsWith('Listeners'); 49 50const isProp = ({ name }: GeneratedData) => name.includes('Props') && name !== 'ErrorRecoveryProps'; 51 52const isComponent = ({ type, extendedTypes, signatures }: GeneratedData) => { 53 if (type?.name && ['React.FC', 'ForwardRefExoticComponent'].includes(type?.name)) { 54 return true; 55 } else if (extendedTypes && extendedTypes.length) { 56 return extendedTypes[0].name === 'Component' || extendedTypes[0].name === 'PureComponent'; 57 } else if (signatures && signatures.length) { 58 if ( 59 signatures[0].type.name === 'Element' || 60 (signatures[0].type.types && signatures[0].type.types.map(t => t.name).includes('Element')) || 61 (signatures[0].parameters && signatures[0].parameters[0].name === 'props') 62 ) { 63 return true; 64 } 65 } 66 return false; 67}; 68 69const isConstant = ({ name, type }: GeneratedData) => 70 !['default', 'Constants', 'EventEmitter'].includes(name) && 71 !(type?.name && ['React.FC', 'ForwardRefExoticComponent'].includes(type?.name)); 72 73const hasCategoryHeader = ({ signatures }: GeneratedData): boolean => 74 (signatures && 75 signatures[0].comment?.blockTags && 76 signatures[0].comment.blockTags.length > 0 && 77 signatures[0].comment.blockTags.filter(tag => tag?.tag === '@header').length > 0) ?? 78 false; 79 80const groupByHeader = (entries: GeneratedData[]) => { 81 return entries.reduce((group: Record<string, GeneratedData[]>, entry) => { 82 const signature = entry.signatures[0]; 83 const header = getCommentContent( 84 signature.comment?.blockTags?.filter(tag => tag.tag === '@header')[0].content ?? [] 85 ); 86 if (header) { 87 group[header] = group[header] ?? []; 88 group[header].push(entry); 89 } 90 return group; 91 }, {}); 92}; 93 94const renderAPI = ( 95 packageName: string, 96 version: string = 'unversioned', 97 apiName?: string, 98 strictTypes: boolean = false, 99 testRequire: any = undefined, 100 headersMapping: Record<string, string> = {} 101): JSX.Element => { 102 try { 103 const { children: data } = testRequire 104 ? testRequire(`~/public/static/data/${version}/${packageName}.json`) 105 : require(`~/public/static/data/${version}/${packageName}.json`); 106 107 const methods = filterDataByKind( 108 data, 109 TypeDocKind.Function, 110 entry => 111 !isListener(entry) && !isHook(entry) && !isComponent(entry) && !hasCategoryHeader(entry) 112 ); 113 const eventSubscriptions = filterDataByKind( 114 data, 115 TypeDocKind.Function, 116 entry => isListener(entry) && !hasCategoryHeader(entry) 117 ); 118 119 const categorizedMethods = groupByHeader( 120 filterDataByKind( 121 data, 122 TypeDocKind.Function, 123 entry => !isComponent(entry) && hasCategoryHeader(entry) 124 ) 125 ); 126 const hasCategorizedMethods = Object.keys(categorizedMethods).length > 0; 127 const hasHeadersMapping = Object.keys(headersMapping).length; 128 129 const types = filterDataByKind( 130 data, 131 TypeDocKind.TypeAlias, 132 entry => 133 !isProp(entry) && 134 !!( 135 entry.type.declaration || 136 entry.type.types || 137 entry.type.type || 138 entry.type.typeArguments 139 ) && 140 (strictTypes && apiName ? entry.name.startsWith(apiName) : true) 141 ); 142 143 const props = filterDataByKind( 144 data, 145 [TypeDocKind.TypeAlias, TypeDocKind.Interface], 146 entry => 147 isProp(entry) && 148 (entry.kind === TypeDocKind.TypeAlias 149 ? !!(entry.type.types || entry.type.declaration?.children) 150 : true) 151 ); 152 const defaultProps = filterDataByKind( 153 data 154 .filter((entry: GeneratedData) => entry.kind === TypeDocKind.Class) 155 .map((entry: GeneratedData) => entry.children) 156 .flat(), 157 TypeDocKind.Property, 158 entry => entry.name === 'defaultProps' 159 )[0]; 160 161 const enums = filterDataByKind(data, TypeDocKind.Enum, entry => entry.name !== 'default'); 162 const interfaces = filterDataByKind( 163 data, 164 TypeDocKind.Interface, 165 entry => !entry.name.includes('Props') 166 ); 167 const constants = filterDataByKind(data, TypeDocKind.Variable, entry => isConstant(entry)); 168 169 const components = filterDataByKind( 170 data, 171 [TypeDocKind.Variable, TypeDocKind.Class, TypeDocKind.Function], 172 entry => isComponent(entry) 173 ); 174 const componentsPropNames = components.map( 175 ({ name, children }) => `${getComponentName(name, children)}Props` 176 ); 177 const componentsProps = filterDataByKind( 178 props, 179 [TypeDocKind.TypeAlias, TypeDocKind.Interface], 180 entry => componentsPropNames.includes(entry.name) 181 ); 182 183 const namespaces = filterDataByKind(data, TypeDocKind.Namespace); 184 185 const classes = filterDataByKind( 186 data, 187 TypeDocKind.Class, 188 entry => !isComponent(entry) && entry.name !== 'default' 189 ); 190 191 const componentsChildren = components 192 .map((cls: ClassDefinitionData) => 193 cls.children?.filter( 194 child => 195 (child?.kind === TypeDocKind.Method || child?.kind === TypeDocKind.Property) && 196 !child.inheritedFrom && 197 child.name !== 'render' && 198 // note(simek): hide unannotated "private" methods 199 !child.name.startsWith('_') 200 ) 201 ) 202 .flat(); 203 204 const methodsNames = methods.map(method => method.name); 205 const staticMethods = componentsChildren.filter( 206 // note(simek): hide duplicate exports from class components 207 method => 208 method?.kind === TypeDocKind.Method && 209 method?.flags?.isStatic === true && 210 !methodsNames.includes(method.name) && 211 !isHook(method as GeneratedData) 212 ); 213 const componentMethods = componentsChildren 214 .filter( 215 method => 216 method?.kind === TypeDocKind.Method && 217 method?.flags?.isStatic !== true && 218 !method?.overwrites 219 ) 220 .filter(Boolean); 221 222 const hooks = filterDataByKind( 223 [...data, ...componentsChildren].filter(Boolean), 224 [TypeDocKind.Function, TypeDocKind.Property], 225 entry => isHook(entry) && !hasCategoryHeader(entry) 226 ); 227 228 return ( 229 <> 230 {hasCategorizedMethods && 231 (hasHeadersMapping 232 ? Object.entries(headersMapping).map(([key, header], index) => ( 233 <APISectionMethods 234 data={categorizedMethods[key]} 235 header={header} 236 key={`${header}-${index}`} 237 /> 238 )) 239 : Object.entries(categorizedMethods).map(([key, data], index) => ( 240 <APISectionMethods data={data} header={key} key={`${key}-${index}`} /> 241 )))} 242 <APISectionComponents data={components} componentsProps={componentsProps} /> 243 <APISectionMethods data={staticMethods} header="Static Methods" /> 244 <APISectionMethods data={componentMethods} header="Component Methods" /> 245 <APISectionConstants data={constants} apiName={apiName} /> 246 <APISectionMethods data={hooks} header="Hooks" /> 247 <APISectionClasses data={classes} /> 248 {props && !componentsProps.length ? ( 249 <APISectionProps data={props} defaultProps={defaultProps} /> 250 ) : null} 251 <APISectionMethods data={methods} apiName={apiName} /> 252 <APISectionMethods 253 data={eventSubscriptions} 254 apiName={apiName} 255 header="Event Subscriptions" 256 /> 257 <APISectionNamespaces data={namespaces} /> 258 <APISectionInterfaces data={interfaces} /> 259 <APISectionTypes data={types} /> 260 <APISectionEnums data={enums} /> 261 </> 262 ); 263 } catch { 264 return <P>No API data file found, sorry!</P>; 265 } 266}; 267 268const APISection = ({ 269 packageName, 270 apiName, 271 forceVersion, 272 strictTypes = false, 273 testRequire = undefined, 274 headersMapping = {}, 275}: Props) => { 276 const { version } = usePageApiVersion(); 277 const resolvedVersion = 278 forceVersion || 279 (version === 'unversioned' ? version : version === 'latest' ? LATEST_VERSION : version); 280 return renderAPI(packageName, resolvedVersion, apiName, strictTypes, testRequire, headersMapping); 281}; 282 283export default APISection; 284