diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js index 1846ebc68267..edf541ef02fb 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local * @format */ diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js index dad386c4c9e7..1aa12f346b27 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local * @format */ diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js index 0b807bbbfa96..f78f64a7d430 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local * @format */ diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js index 891f4fa61037..c5ab32a5d3d8 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local * @format */ diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index 1528399b0666..4509869ff85b 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. * * @format - * @flow strict + * @flow strict-local */ 'use strict'; @@ -28,6 +28,27 @@ const { UnsupportedObjectPropertyTypeAnnotationParserError, } = require('./errors'); const invariant = require('invariant'); +const {getTypes, isModuleRegistryCall} = require('./flow/utils'); +const { + throwIfIncorrectModuleRegistryCallTypeParameterParserError, + throwIfModuleInterfaceIsMisnamed, + throwIfModuleInterfaceNotFound, + throwIfMoreThanOneModuleInterfaceParserError, + throwIfMoreThanOneModuleRegistryCalls, + throwIfUntypedModule, + throwIfUnusedModuleInterfaceParserError, + throwIfWrongNumberOfCallExpressionArgs, +} = require('./error-utils'); +const {verifyPlatforms, visit} = require('./utils'); +const { + IncorrectModuleRegistryCallArgumentTypeParserError, +} = require('./errors'); +import type { + NativeModuleAliasMap, + NativeModulePropertyShape, +} from '../CodegenSchema'; +import type {ParserErrorCapturer} from './utils'; +import type {Parser} from './parser'; function wrapModuleSchema( nativeModuleSchema: NativeModuleSchema, @@ -157,6 +178,186 @@ function emitUnionTypeAnnotation( }); } +function buildModuleSchema( + hasteModuleName: string, + /** + * TODO(T108222691): Use flow-types for @babel/parser + */ + ast: $FlowFixMe, + tryParse: ParserErrorCapturer, + language: ParserType, + parser: Parser, +): NativeModuleSchema { + const types = getTypes(ast); + + const moduleSpecs = (Object.values(types): $ReadOnlyArray<$FlowFixMe>).filter( + //$FlowFixMe + isModuleInterface, + ); + + throwIfModuleInterfaceNotFound( + moduleSpecs.length, + hasteModuleName, + ast, + language, + ); + + throwIfMoreThanOneModuleInterfaceParserError( + hasteModuleName, + moduleSpecs, + language, + ); + + const [moduleSpec] = moduleSpecs; + + throwIfModuleInterfaceIsMisnamed(hasteModuleName, moduleSpec.id, language); + + // Parse Module Names + const moduleName = tryParse((): string => { + const callExpressions = []; + visit(ast, { + CallExpression(node) { + if (isModuleRegistryCall(node)) { + callExpressions.push(node); + } + }, + }); + + throwIfUnusedModuleInterfaceParserError( + hasteModuleName, + moduleSpec, + callExpressions, + language, + ); + + throwIfMoreThanOneModuleRegistryCalls( + hasteModuleName, + callExpressions, + callExpressions.length, + language, + ); + + const [callExpression] = callExpressions; + const typeParams = + language === 'TypeScript' + ? callExpression.typeParameters + : callExpression.typeArguments; + const methodName = callExpression.callee.property.name; + const callExpressionArgumentType = + language === 'TypeScript' ? 'StringLiteral' : 'Literal'; + + throwIfWrongNumberOfCallExpressionArgs( + hasteModuleName, + callExpression, + methodName, + callExpression.arguments.length, + language, + ); + + if (callExpression.arguments[0].type !== callExpressionArgumentType) { + const {type} = callExpression.arguments[0]; + throw new IncorrectModuleRegistryCallArgumentTypeParserError( + hasteModuleName, + callExpression.arguments[0], + methodName, + type, + language, + ); + } + + const $moduleName = callExpression.arguments[0].value; + + throwIfUntypedModule( + typeParams, + hasteModuleName, + callExpression, + methodName, + $moduleName, + language, + ); + + throwIfIncorrectModuleRegistryCallTypeParameterParserError( + hasteModuleName, + typeParams, + methodName, + $moduleName, + language, + ); + + return $moduleName; + }); + + const moduleNames = moduleName == null ? [] : [moduleName]; + + // 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, + moduleNames, + ); + + let filteredModuleSpec; + if (language === 'TypeScript') { + filteredModuleSpec = moduleSpec.body.body.filter( + property => + property.type === 'TSMethodSignature' || + property.type === 'TSPropertySignature', + ); + } else { + filteredModuleSpec = moduleSpec.body.properties.filter( + property => property.type === 'ObjectTypeProperty', + ); + } + + // $FlowFixMe[missing-type-arg] + return filteredModuleSpec + .map(property => { + const aliasMap: {...NativeModuleAliasMap} = {}; + + return tryParse(() => ({ + aliasMap: aliasMap, + // $FlowFixMe + propertyShape: buildPropertySchema( + hasteModuleName, + property, + types, + aliasMap, + tryParse, + cxxOnly, + language, + parser, + ), + })); + }) + .filter(Boolean) + .reduce( + (moduleSchema: NativeModuleSchema, {aliasMap, propertyShape}) => { + return { + type: 'NativeModule', + aliases: {...moduleSchema.aliases, ...aliasMap}, + spec: { + properties: [...moduleSchema.spec.properties, propertyShape], + }, + moduleNames: moduleSchema.moduleNames, + excludedPlatforms: moduleSchema.excludedPlatforms, + }; + }, + { + type: 'NativeModule', + aliases: {}, + spec: {properties: []}, + moduleNames: moduleNames, + excludedPlatforms: + excludedPlatforms.length !== 0 ? [...excludedPlatforms] : undefined, + }, + ); +} + function getKeyName( propertyOrIndex: $FlowFixMe, hasteModuleName: string, @@ -189,5 +390,6 @@ module.exports = { assertGenericTypeAnnotationHasExactlyOneTypeParameter, emitMixedTypeAnnotation, emitUnionTypeAnnotation, + buildModuleSchema, getKeyName, };