UNPKG

@react-native/codegen

Version:
1,184 lines (1,167 loc) • 32.9 kB
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * * @format */ 'use strict'; const { throwIfConfigNotfound, throwIfEventEmitterEventTypeIsUnsupported, throwIfEventEmitterTypeIsUnsupported, throwIfIncorrectModuleRegistryCallArgument, throwIfIncorrectModuleRegistryCallTypeParameterParserError, throwIfModuleInterfaceIsMisnamed, throwIfModuleInterfaceNotFound, throwIfModuleTypeIsUnsupported, throwIfMoreThanOneCodegenNativecommands, throwIfMoreThanOneConfig, throwIfMoreThanOneModuleInterfaceParserError, throwIfMoreThanOneModuleRegistryCalls, throwIfPropertyValueTypeIsUnsupported, throwIfTypeAliasIsNotInterface, throwIfUnsupportedFunctionParamTypeAnnotationParserError, throwIfUnsupportedFunctionReturnTypeAnnotationParserError, throwIfUntypedModule, throwIfUnusedModuleInterfaceParserError, throwIfWrongNumberOfCallExpressionArgs, } = require('./error-utils'); const { MissingTypeParameterGenericParserError, MoreThanOneTypeParameterGenericParserError, UnnamedFunctionParamParserError, UnsupportedObjectDirectRecursivePropertyParserError, } = require('./errors'); const { createParserErrorCapturer, extractNativeModuleName, getConfigType, getSortedObject, isModuleRegistryCall, verifyPlatforms, visit, } = require('./utils'); const invariant = require('invariant'); // $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser function wrapModuleSchema(nativeModuleSchema, hasteModuleName) { return { modules: { [hasteModuleName]: nativeModuleSchema, }, }; } // $FlowFixMe[unsupported-variance-annotation] function unwrapNullable(x) { if (x.type === 'NullableTypeAnnotation') { return [x.typeAnnotation, true]; } return [x, false]; } // $FlowFixMe[unsupported-variance-annotation] function wrapNullable(nullable, typeAnnotation) { if (!nullable) { return typeAnnotation; } return { type: 'NullableTypeAnnotation', typeAnnotation, }; } function assertGenericTypeAnnotationHasExactlyOneTypeParameter( moduleName, /** * TODO(T108222691): Use flow-types for @babel/parser */ typeAnnotation, parser, ) { if (typeAnnotation.typeParameters == null) { throw new MissingTypeParameterGenericParserError( moduleName, typeAnnotation, parser, ); } const typeAnnotationType = parser.typeParameterInstantiation; invariant( typeAnnotation.typeParameters.type === typeAnnotationType, `assertGenericTypeAnnotationHasExactlyOneTypeParameter: Type parameters must be an AST node of type '${typeAnnotationType}'`, ); if (typeAnnotation.typeParameters.params.length !== 1) { throw new MoreThanOneTypeParameterGenericParserError( moduleName, typeAnnotation, parser, ); } } function isObjectProperty(property, language) { switch (language) { case 'Flow': return property.type === 'ObjectTypeProperty'; case 'TypeScript': return property.type === 'TSPropertySignature'; default: return false; } } function getObjectTypeAnnotations( hasteModuleName, types, tryParse, translateTypeAnnotation, parser, ) { const aliasMap = {}; Object.entries(types).forEach(([key, value]) => { const isTypeAlias = value.type === 'TypeAlias' || value.type === 'TSTypeAliasDeclaration'; if (!isTypeAlias) { return; } const parent = parser.nextNodeForTypeAlias(value); if ( parent.type !== 'ObjectTypeAnnotation' && parent.type !== 'TSTypeLiteral' ) { return; } const typeProperties = parser .getAnnotatedElementProperties(value) .map(prop => parseObjectProperty( parent, prop, hasteModuleName, types, aliasMap, {}, // enumMap tryParse, true, // cxxOnly (prop === null || prop === void 0 ? void 0 : prop.optional) || false, translateTypeAnnotation, parser, ), ); aliasMap[key] = { type: 'ObjectTypeAnnotation', properties: typeProperties, }; }); return aliasMap; } function parseObjectProperty( parentObject, property, hasteModuleName, types, aliasMap, enumMap, tryParse, cxxOnly, nullable, translateTypeAnnotation, parser, ) { const language = parser.language(); const name = parser.getKeyName(property, hasteModuleName); const {optional = false} = property; const languageTypeAnnotation = language === 'TypeScript' ? property.typeAnnotation.typeAnnotation : property.value; // Handle recursive types if (parentObject) { var _languageTypeAnnotati, _languageTypeAnnotati2; const propertyType = parser.getResolveTypeAnnotationFN()( languageTypeAnnotation, types, parser, ); if ( propertyType.typeResolutionStatus.successful === true && propertyType.typeResolutionStatus.type === 'alias' && (language === 'TypeScript' ? parentObject.typeName && parentObject.typeName.name === ((_languageTypeAnnotati = languageTypeAnnotation.typeName) === null || _languageTypeAnnotati === void 0 ? void 0 : _languageTypeAnnotati.name) : parentObject.id && parentObject.id.name === ((_languageTypeAnnotati2 = languageTypeAnnotation.id) === null || _languageTypeAnnotati2 === void 0 ? void 0 : _languageTypeAnnotati2.name)) ) { if (!optional) { throw new UnsupportedObjectDirectRecursivePropertyParserError( name, languageTypeAnnotation, hasteModuleName, ); } return { name, optional, typeAnnotation: { type: 'TypeAliasTypeAnnotation', name: propertyType.typeResolutionStatus.name, }, }; } } // Handle non-recursive types const [propertyTypeAnnotation, isPropertyNullable] = unwrapNullable( translateTypeAnnotation( hasteModuleName, languageTypeAnnotation, types, aliasMap, enumMap, tryParse, cxxOnly, parser, ), ); if ( (propertyTypeAnnotation.type === 'FunctionTypeAnnotation' && !cxxOnly) || propertyTypeAnnotation.type === 'PromiseTypeAnnotation' || propertyTypeAnnotation.type === 'VoidTypeAnnotation' ) { throwIfPropertyValueTypeIsUnsupported( hasteModuleName, languageTypeAnnotation, property.key, propertyTypeAnnotation.type, ); } return { name, optional, typeAnnotation: wrapNullable(isPropertyNullable, propertyTypeAnnotation), }; } function translateFunctionTypeAnnotation( hasteModuleName, // TODO(T108222691): Use flow-types for @babel/parser // TODO(T71778680): This is a FunctionTypeAnnotation. Type this. functionTypeAnnotation, types, aliasMap, enumMap, tryParse, cxxOnly, translateTypeAnnotation, parser, ) { const params = []; for (const param of parser.getFunctionTypeAnnotationParameters( functionTypeAnnotation, )) { const parsedParam = tryParse(() => { if (parser.getFunctionNameFromParameter(param) == null) { throw new UnnamedFunctionParamParserError(param, hasteModuleName); } const paramName = parser.getParameterName(param); const [paramTypeAnnotation, isParamTypeAnnotationNullable] = unwrapNullable( translateTypeAnnotation( hasteModuleName, parser.getParameterTypeAnnotation(param), types, aliasMap, enumMap, tryParse, cxxOnly, parser, ), ); if ( paramTypeAnnotation.type === 'VoidTypeAnnotation' || paramTypeAnnotation.type === 'PromiseTypeAnnotation' ) { return throwIfUnsupportedFunctionParamTypeAnnotationParserError( hasteModuleName, param.typeAnnotation, paramName, paramTypeAnnotation.type, ); } return { name: paramName, optional: Boolean(param.optional), typeAnnotation: wrapNullable( isParamTypeAnnotationNullable, paramTypeAnnotation, ), }; }); if (parsedParam != null) { params.push(parsedParam); } } const [returnTypeAnnotation, isReturnTypeAnnotationNullable] = unwrapNullable( translateTypeAnnotation( hasteModuleName, parser.getFunctionTypeAnnotationReturnType(functionTypeAnnotation), types, aliasMap, enumMap, tryParse, cxxOnly, parser, ), ); throwIfUnsupportedFunctionReturnTypeAnnotationParserError( hasteModuleName, functionTypeAnnotation, 'FunctionTypeAnnotation', cxxOnly, returnTypeAnnotation.type, ); return { type: 'FunctionTypeAnnotation', returnTypeAnnotation: wrapNullable( isReturnTypeAnnotationNullable, returnTypeAnnotation, ), params, }; } function buildPropertySchema( hasteModuleName, // TODO(T108222691): [TS] Use flow-types for @babel/parser // TODO(T71778680): [Flow] This is an ObjectTypeProperty containing either: // - a FunctionTypeAnnotation or GenericTypeAnnotation // - a NullableTypeAnnoation containing a FunctionTypeAnnotation or GenericTypeAnnotation // Flow type this node property, types, aliasMap, enumMap, tryParse, cxxOnly, translateTypeAnnotation, parser, ) { let nullable = false; let {key, value} = property; const methodName = key.name; if (parser.language() === 'TypeScript') { value = property.type === 'TSMethodSignature' ? property : property.typeAnnotation; } const resolveTypeAnnotationFN = parser.getResolveTypeAnnotationFN(); ({nullable, typeAnnotation: value} = resolveTypeAnnotationFN( value, types, parser, )); throwIfModuleTypeIsUnsupported( hasteModuleName, property.value, key.name, value.type, parser, ); return { name: methodName, optional: Boolean(property.optional), typeAnnotation: wrapNullable( nullable, translateFunctionTypeAnnotation( hasteModuleName, value, types, aliasMap, enumMap, tryParse, cxxOnly, translateTypeAnnotation, parser, ), ), }; } function buildEventEmitterSchema( hasteModuleName, // TODO(T108222691): [TS] Use flow-types for @babel/parser // TODO(T71778680): [Flow] This is an ObjectTypeProperty containing either: // - a FunctionTypeAnnotation or GenericTypeAnnotation // - a NullableTypeAnnoation containing a FunctionTypeAnnotation or GenericTypeAnnotation // Flow type this node property, types, aliasMap, enumMap, tryParse, cxxOnly, translateTypeAnnotation, parser, ) { const {key} = property; const value = parser.language() === 'TypeScript' ? property.typeAnnotation.typeAnnotation : property.value; const eventemitterName = key.name; const resolveTypeAnnotationFN = parser.getResolveTypeAnnotationFN(); const [typeAnnotation, typeAnnotationNullable] = unwrapNullable(value); const typeAnnotationUntyped = value.typeParameters.params.length === 1 && parser.language() === 'TypeScript' ? value.typeParameters.params[0].type === 'TSTypeLiteral' && value.typeParameters.params[0].members.length === 0 : value.typeParameters.params[0].type === 'ObjectTypeAnnotation' && value.typeParameters.params[0].properties.length === 0; throwIfEventEmitterTypeIsUnsupported( hasteModuleName, key.name, typeAnnotation.type, parser, typeAnnotationNullable, typeAnnotationUntyped, ); const eventTypeResolutionStatus = resolveTypeAnnotationFN( typeAnnotation.typeParameters.params[0], types, parser, ); throwIfEventEmitterEventTypeIsUnsupported( hasteModuleName, key.name, eventTypeResolutionStatus.typeAnnotation, parser, eventTypeResolutionStatus.nullable, ); const eventTypeAnnotation = translateTypeAnnotation( hasteModuleName, typeAnnotation.typeParameters.params[0], types, aliasMap, enumMap, tryParse, cxxOnly, parser, ); return { name: eventemitterName, optional: false, typeAnnotation: { type: 'EventEmitterTypeAnnotation', typeAnnotation: eventTypeAnnotation, }, }; } function buildSchemaFromConfigType( configType, filename, ast, wrapComponentSchema, buildComponentSchema, buildModuleSchema, parser, translateTypeAnnotation, ) { switch (configType) { case 'component': { return wrapComponentSchema(buildComponentSchema(ast, parser)); } case 'module': { if (filename === undefined || filename === null) { throw new Error('Filepath expected while parasing a module'); } const nativeModuleName = extractNativeModuleName(filename); const [parsingErrors, tryParse] = createParserErrorCapturer(); const schema = tryParse(() => buildModuleSchema( nativeModuleName, ast, tryParse, parser, translateTypeAnnotation, ), ); if (parsingErrors.length > 0) { /** * TODO(T77968131): We have two options: * - Throw the first error, but indicate there are more then one errors. * - Display all errors, nicely formatted. * * For the time being, we're just throw the first error. **/ throw parsingErrors[0]; } invariant( schema != null, 'When there are no parsing errors, the schema should not be null', ); return wrapModuleSchema(schema, nativeModuleName); } default: return { modules: {}, }; } } function buildSchema( contents, filename, wrapComponentSchema, buildComponentSchema, buildModuleSchema, Visitor, parser, translateTypeAnnotation, ) { // Early return for non-Spec JavaScript files if ( !contents.includes('codegenNativeComponent') && !contents.includes('TurboModule') ) { return { modules: {}, }; } const ast = parser.getAst(contents, filename); const configType = getConfigType(ast, Visitor); return buildSchemaFromConfigType( configType, filename, ast, wrapComponentSchema, buildComponentSchema, buildModuleSchema, parser, translateTypeAnnotation, ); } function createComponentConfig(foundConfig, commandsTypeNames) { return { ...foundConfig, commandTypeName: commandsTypeNames[0] == null ? null : commandsTypeNames[0].commandTypeName, commandOptionsExpression: commandsTypeNames[0] == null ? null : commandsTypeNames[0].commandOptionsExpression, }; } const parseModuleName = (hasteModuleName, moduleSpec, ast, parser) => { const callExpressions = []; visit(ast, { CallExpression(node) { if (isModuleRegistryCall(node)) { callExpressions.push(node); } }, }); throwIfUnusedModuleInterfaceParserError( hasteModuleName, moduleSpec, callExpressions, ); throwIfMoreThanOneModuleRegistryCalls( hasteModuleName, callExpressions, callExpressions.length, ); const [callExpression] = callExpressions; const typeParameters = parser.callExpressionTypeParameters(callExpression); const methodName = callExpression.callee.property.name; throwIfWrongNumberOfCallExpressionArgs( hasteModuleName, callExpression, methodName, callExpression.arguments.length, ); throwIfIncorrectModuleRegistryCallArgument( hasteModuleName, callExpression.arguments[0], methodName, ); const $moduleName = callExpression.arguments[0].value; throwIfUntypedModule( typeParameters, hasteModuleName, callExpression, methodName, $moduleName, ); throwIfIncorrectModuleRegistryCallTypeParameterParserError( hasteModuleName, typeParameters, methodName, $moduleName, parser, ); return $moduleName; }; const buildModuleSchema = ( hasteModuleName, ast, tryParse, parser, translateTypeAnnotation, ) => { const language = parser.language(); const types = parser.getTypes(ast); const moduleSpecs = Object.values(types).filter(t => parser.isModuleInterface(t), ); throwIfModuleInterfaceNotFound( moduleSpecs.length, hasteModuleName, ast, language, ); throwIfMoreThanOneModuleInterfaceParserError( hasteModuleName, moduleSpecs, language, ); const [moduleSpec] = moduleSpecs; throwIfModuleInterfaceIsMisnamed(hasteModuleName, moduleSpec.id, language); // Parse Module Name const moduleName = parseModuleName(hasteModuleName, moduleSpec, ast, parser); // Some module names use platform suffix to indicate platform-exclusive modules. // Eventually this should be made explicit in the Flow type itself. // Also check the hasteModuleName for platform suffix. // Note: this shape is consistent with ComponentSchema. const {cxxOnly, excludedPlatforms} = verifyPlatforms( hasteModuleName, moduleName, ); const aliasMap = cxxOnly ? getObjectTypeAnnotations( hasteModuleName, types, tryParse, translateTypeAnnotation, parser, ) : {}; const properties = language === 'Flow' ? moduleSpec.body.properties : moduleSpec.body.body; // $FlowFixMe[missing-type-arg] const nativeModuleSchema = properties .filter( property => property.type === 'ObjectTypeProperty' || property.type === 'TSPropertySignature' || property.type === 'TSMethodSignature', ) .map(property => { var _property$typeAnnotat, _property$value; const enumMap = {}; const isEventEmitter = language === 'TypeScript' ? (property === null || property === void 0 ? void 0 : property.type) === 'TSPropertySignature' && parser.getTypeAnnotationName( property === null || property === void 0 || (_property$typeAnnotat = property.typeAnnotation) === null || _property$typeAnnotat === void 0 ? void 0 : _property$typeAnnotat.typeAnnotation, ) === 'EventEmitter' : (property === null || property === void 0 || (_property$value = property.value) === null || _property$value === void 0 ? void 0 : _property$value.type) === 'GenericTypeAnnotation' && parser.getTypeAnnotationName( property === null || property === void 0 ? void 0 : property.value, ) === 'EventEmitter'; return tryParse(() => ({ aliasMap, enumMap, propertyShape: isEventEmitter ? { type: 'eventEmitter', value: buildEventEmitterSchema( hasteModuleName, property, types, aliasMap, enumMap, tryParse, cxxOnly, translateTypeAnnotation, parser, ), } : { type: 'method', value: buildPropertySchema( hasteModuleName, property, types, aliasMap, enumMap, tryParse, cxxOnly, translateTypeAnnotation, parser, ), }, })); }) .filter(Boolean) .reduce( (moduleSchema, {enumMap, propertyShape}) => ({ type: 'NativeModule', aliasMap: { ...moduleSchema.aliasMap, ...aliasMap, }, enumMap: { ...moduleSchema.enumMap, ...enumMap, }, spec: { eventEmitters: [...moduleSchema.spec.eventEmitters].concat( propertyShape.type === 'eventEmitter' ? [propertyShape.value] : [], ), methods: [...moduleSchema.spec.methods].concat( propertyShape.type === 'method' ? [propertyShape.value] : [], ), }, moduleName: moduleSchema.moduleName, excludedPlatforms: moduleSchema.excludedPlatforms, }), { type: 'NativeModule', aliasMap: {}, enumMap: {}, spec: { eventEmitters: [], methods: [], }, moduleName, excludedPlatforms: excludedPlatforms.length !== 0 ? [...excludedPlatforms] : undefined, }, ); return { type: 'NativeModule', aliasMap: getSortedObject(nativeModuleSchema.aliasMap), enumMap: getSortedObject(nativeModuleSchema.enumMap), spec: { eventEmitters: nativeModuleSchema.spec.eventEmitters.sort(), methods: nativeModuleSchema.spec.methods.sort(), }, moduleName, excludedPlatforms: nativeModuleSchema.excludedPlatforms, }; }; /** * This function is used to find the type of a native component * provided the default exports statement from generated AST. * @param statement The statement to be parsed. * @param foundConfigs The 'mutable' array of configs that have been found. * @param parser The language parser to be used. * @returns void */ function findNativeComponentType(statement, foundConfigs, parser) { let declaration = statement.declaration; // codegenNativeComponent can be nested inside a cast // expression so we need to go one level deeper if ( declaration.type === 'TSAsExpression' || declaration.type === 'AsExpression' || declaration.type === 'TypeCastExpression' ) { declaration = declaration.expression; } try { if (declaration.callee.name === 'codegenNativeComponent') { const typeArgumentParams = parser.getTypeArgumentParamsFromDeclaration(declaration); const funcArgumentParams = declaration.arguments; const nativeComponentType = parser.getNativeComponentType( typeArgumentParams, funcArgumentParams, ); if (funcArgumentParams.length > 1) { nativeComponentType.optionsExpression = funcArgumentParams[1]; } foundConfigs.push(nativeComponentType); } } catch (e) { // ignore } } function getCommandOptions(commandOptionsExpression) { if (commandOptionsExpression == null) { return null; } let foundOptions; try { foundOptions = commandOptionsExpression.properties.reduce( (options, prop) => { options[prop.key.name] = ( (prop && prop.value && prop.value.elements) || [] ).map(element => element && element.value); return options; }, {}, ); } catch (e) { throw new Error( 'Failed to parse command options, please check that they are defined correctly', ); } return foundOptions; } function getOptions(optionsExpression) { if (!optionsExpression) { return null; } let foundOptions; try { foundOptions = optionsExpression.properties.reduce((options, prop) => { if (prop.value.type === 'ArrayExpression') { options[prop.key.name] = prop.value.elements.map( element => element.value, ); } else { options[prop.key.name] = prop.value.value; } return options; }, {}); } catch (e) { throw new Error( 'Failed to parse codegen options, please check that they are defined correctly', ); } if ( foundOptions.paperComponentName && foundOptions.paperComponentNameDeprecated ) { throw new Error( 'Failed to parse codegen options, cannot use both paperComponentName and paperComponentNameDeprecated', ); } return foundOptions; } function getCommandTypeNameAndOptionsExpression(namedExport, parser) { let callExpression; let calleeName; try { callExpression = namedExport.declaration.declarations[0].init; calleeName = callExpression.callee.name; } catch (e) { return; } if (calleeName !== 'codegenNativeCommands') { return; } if (callExpression.arguments.length !== 1) { throw new Error( 'codegenNativeCommands must be passed options including the supported commands', ); } const typeArgumentParam = parser.getTypeArgumentParamsFromDeclaration(callExpression)[0]; if (!parser.isGenericTypeAnnotation(typeArgumentParam.type)) { throw new Error( "codegenNativeCommands doesn't support inline definitions. Specify a file local type alias", ); } return { commandTypeName: parser.getTypeAnnotationName(typeArgumentParam), commandOptionsExpression: callExpression.arguments[0], }; } function propertyNames(properties) { return properties .map(property => property && property.key && property.key.name) .filter(Boolean); } function extendsForProp(prop, types, parser) { const argument = parser.argumentForProp(prop); if (!argument) { console.log('null', prop); } const name = parser.nameForArgument(prop); if (types[name] != null) { // This type is locally defined in the file return null; } switch (name) { case 'ViewProps': return { type: 'ReactNativeBuiltInType', knownTypeName: 'ReactNativeCoreViewProps', }; default: { throw new Error(`Unable to handle prop spread: ${name}`); } } } function buildPropSchema(property, types, parser) { const getSchemaInfoFN = parser.getGetSchemaInfoFN(); const info = getSchemaInfoFN(property, types, parser); if (info == null) { return null; } const {name, optional, typeAnnotation, defaultValue, withNullDefault} = info; const getTypeAnnotationFN = parser.getGetTypeAnnotationFN(); return { name, optional, typeAnnotation: getTypeAnnotationFN( name, typeAnnotation, defaultValue, withNullDefault, types, parser, buildPropSchema, ), }; } /* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's * LTI update could not be added via codemod */ function getEventArgument(argumentProps, parser, getPropertyType) { return { type: 'ObjectTypeAnnotation', properties: argumentProps.map(member => buildPropertiesForEvent(member, parser, getPropertyType), ), }; } /* $FlowFixMe[signature-verification-failure] there's no flowtype for AST. * TODO(T108222691): Use flow-types for @babel/parser */ function findComponentConfig(ast, parser) { const foundConfigs = []; const defaultExports = ast.body.filter( node => node.type === 'ExportDefaultDeclaration', ); defaultExports.forEach(statement => { findNativeComponentType(statement, foundConfigs, parser); }); throwIfConfigNotfound(foundConfigs); throwIfMoreThanOneConfig(foundConfigs); const foundConfig = foundConfigs[0]; const namedExports = ast.body.filter( node => node.type === 'ExportNamedDeclaration', ); const commandsTypeNames = namedExports .map(statement => getCommandTypeNameAndOptionsExpression(statement, parser)) .filter(Boolean); throwIfMoreThanOneCodegenNativecommands(commandsTypeNames); return createComponentConfig(foundConfig, commandsTypeNames); } // $FlowFixMe[signature-verification-failure] there's no flowtype for AST function getCommandProperties(ast, parser) { const {commandTypeName, commandOptionsExpression} = findComponentConfig( ast, parser, ); if (commandTypeName == null) { return []; } const types = parser.getTypes(ast); const typeAlias = types[commandTypeName]; throwIfTypeAliasIsNotInterface(typeAlias, parser); const properties = parser.bodyProperties(typeAlias); if (!properties) { throw new Error( `Failed to find type definition for "${commandTypeName}", please check that you have a valid codegen file`, ); } const commandPropertyNames = propertyNames(properties); const commandOptions = getCommandOptions(commandOptionsExpression); if (commandOptions == null || commandOptions.supportedCommands == null) { throw new Error( 'codegenNativeCommands must be given an options object with supportedCommands array', ); } if ( commandOptions.supportedCommands.length !== commandPropertyNames.length || !commandOptions.supportedCommands.every(supportedCommand => commandPropertyNames.includes(supportedCommand), ) ) { throw new Error( `codegenNativeCommands expected the same supportedCommands specified in the ${commandTypeName} interface: ${commandPropertyNames.join( ', ', )}`, ); } return properties; } function getTypeResolutionStatus(type, typeAnnotation, parser) { return { successful: true, type, name: parser.getTypeAnnotationName(typeAnnotation), }; } function handleGenericTypeAnnotation( typeAnnotation, resolvedTypeAnnotation, parser, ) { let typeResolutionStatus; let node; switch (resolvedTypeAnnotation.type) { case parser.typeAlias: { typeResolutionStatus = getTypeResolutionStatus( 'alias', typeAnnotation, parser, ); node = parser.nextNodeForTypeAlias(resolvedTypeAnnotation); break; } case parser.enumDeclaration: { typeResolutionStatus = getTypeResolutionStatus( 'enum', typeAnnotation, parser, ); node = parser.nextNodeForEnum(resolvedTypeAnnotation); break; } // parser.interfaceDeclaration is not used here because for flow it should fall through to default case and throw an error case 'TSInterfaceDeclaration': { typeResolutionStatus = getTypeResolutionStatus( 'alias', typeAnnotation, parser, ); node = resolvedTypeAnnotation; break; } default: { throw new TypeError( parser.genericTypeAnnotationErrorMessage(resolvedTypeAnnotation), ); } } return { typeAnnotation: node, typeResolutionStatus, }; } function buildPropertiesForEvent(property, parser, getPropertyType) { const name = property.key.name; const optional = parser.isOptionalProperty(property); const typeAnnotation = parser.getTypeAnnotationFromProperty(property); return getPropertyType(name, optional, typeAnnotation, parser); } function verifyPropNotAlreadyDefined(props, needleProp) { const propName = needleProp.key.name; const foundProp = props.some(prop => prop.key.name === propName); if (foundProp) { throw new Error(`A prop was already defined with the name ${propName}`); } } function handleEventHandler( name, typeAnnotation, parser, types, findEventArgumentsAndType, ) { const eventType = name === 'BubblingEventHandler' ? 'bubble' : 'direct'; const paperTopLevelNameDeprecated = parser.getPaperTopLevelNameDeprecated(typeAnnotation); switch (typeAnnotation.typeParameters.params[0].type) { case parser.nullLiteralTypeAnnotation: case parser.undefinedLiteralTypeAnnotation: return { argumentProps: [], bubblingType: eventType, paperTopLevelNameDeprecated, }; default: return findEventArgumentsAndType( parser, typeAnnotation.typeParameters.params[0], types, eventType, paperTopLevelNameDeprecated, ); } } function emitBuildEventSchema( paperTopLevelNameDeprecated, name, optional, nonNullableBubblingType, argument, ) { if (paperTopLevelNameDeprecated != null) { return { name, optional, bubblingType: nonNullableBubblingType, paperTopLevelNameDeprecated, typeAnnotation: { type: 'EventTypeAnnotation', argument: argument, }, }; } return { name, optional, bubblingType: nonNullableBubblingType, typeAnnotation: { type: 'EventTypeAnnotation', argument: argument, }, }; } module.exports = { wrapModuleSchema, unwrapNullable, wrapNullable, assertGenericTypeAnnotationHasExactlyOneTypeParameter, isObjectProperty, parseObjectProperty, translateFunctionTypeAnnotation, buildPropertySchema, buildSchemaFromConfigType, buildSchema, createComponentConfig, parseModuleName, buildModuleSchema, findNativeComponentType, propertyNames, getCommandOptions, getOptions, getCommandTypeNameAndOptionsExpression, extendsForProp, buildPropSchema, getEventArgument, findComponentConfig, getCommandProperties, handleGenericTypeAnnotation, getTypeResolutionStatus, buildPropertiesForEvent, verifyPropNotAlreadyDefined, handleEventHandler, emitBuildEventSchema, };