From ec094e75bd2b2ef5e1520f3b54b85d6db85efcc1 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Sat, 17 Oct 2020 02:43:02 -0700 Subject: [PATCH] Codegen: denote Android/iOS exclusive platform modules in the schema Summary: Some existing NativeModules have either Android or IOS suffix to denote the exclusive intent for that platform. For now, note this in the codegen schema output, so that the generator can skip irrelevant modules. Long term, each Flow type for module Spec should denote the intended/excluded platforms directly. Changelog: [Internal] Reviewed By: RSNara Differential Revision: D24370568 fbshipit-source-id: 8f725bdb39107d73c1aba0689db7f47ed7c374b0 --- .../react-native-codegen/src/CodegenSchema.js | 4 ++ .../src/parsers/flow/index.js | 16 +++--- .../modules/__test_fixtures__/fixtures.js | 52 ++++++++++++++++++- .../module-parser-snapshot-test.js.snap | 40 ++++++++++++++ .../src/parsers/flow/modules/index.js | 25 +++++++-- 5 files changed, 124 insertions(+), 13 deletions(-) diff --git a/packages/react-native-codegen/src/CodegenSchema.js b/packages/react-native-codegen/src/CodegenSchema.js index 84a2d5e2fd9..9370d09dedd 100644 --- a/packages/react-native-codegen/src/CodegenSchema.js +++ b/packages/react-native-codegen/src/CodegenSchema.js @@ -264,6 +264,10 @@ export type NativeModuleSchema = $ReadOnly<{| aliases: NativeModuleAliasMap, spec: NativeModuleSpec, moduleNames: $ReadOnlyArray, + // Use for modules that are not used on other platforms. + // TODO: It's clearer to define `restrictedToPlatforms` instead, but + // `excludedPlatforms` is used here to be consistent with ComponentSchema. + excludedPlatforms?: $ReadOnlyArray, |}>; type NativeModuleSpec = $ReadOnly<{| diff --git a/packages/react-native-codegen/src/parsers/flow/index.js b/packages/react-native-codegen/src/parsers/flow/index.js index acb63944ce2..ab3a9d4a595 100644 --- a/packages/react-native-codegen/src/parsers/flow/index.js +++ b/packages/react-native-codegen/src/parsers/flow/index.js @@ -152,12 +152,12 @@ function buildSchema(contents: string, filename: ?string): SchemaType { if (filename === undefined || filename === null) { throw new Error('Filepath expected while parasing a module'); } - const moduleName = path.basename(filename).replace(/\.js$/, ''); + const hasteModuleName = path.basename(filename).replace(/\.js$/, ''); const regex = new RegExp(TURBO_MODULE_REGISTRY_REQUIRE_REGEX_STRING, 'g'); let match = regex.exec(contents); - const errorHeader = `Error while parsing Module '${moduleName}'`; + const errorHeader = `Error while parsing Module '${hasteModuleName}'`; if (match == null) { throw new Error( @@ -165,23 +165,23 @@ function buildSchema(contents: string, filename: ?string): SchemaType { ); } - const moduleRequires = []; + const moduleNames = []; while (match != null) { const resultGroups = match.groups; invariant( resultGroups != null, - `Couldn't parse TurboModuleRegistry.(get|getEnforcing) call in module '${moduleName}'.`, + `Couldn't parse TurboModuleRegistry.(get|getEnforcing) call in module '${hasteModuleName}'.`, ); - if (!moduleRequires.includes(resultGroups.nativeModuleName)) { - moduleRequires.push(resultGroups.nativeModuleName); + if (!moduleNames.includes(resultGroups.nativeModuleName)) { + moduleNames.push(resultGroups.nativeModuleName); } match = regex.exec(contents); } return wrapModuleSchema( - buildModuleSchema(moduleName, moduleRequires, types), - moduleName, + buildModuleSchema(hasteModuleName, moduleNames, types), + hasteModuleName, ); } } diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js index 7a9b29a3d86..9ef58d4d097 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js @@ -27,7 +27,7 @@ import type {TurboModule} from '../RCTExport'; import * as TurboModuleRegistry from '../TurboModuleRegistry'; export interface Spec extends TurboModule { - // mo methods + // no methods } export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); @@ -497,6 +497,54 @@ export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); `; +const ANDROID_ONLY_NATIVE_MODULE = ` +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +import type {TurboModule} from '../RCTExport'; +import * as TurboModuleRegistry from '../TurboModuleRegistry'; + +export interface Spec extends TurboModule { + // no methods +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModuleAndroid'); + +`; + +const IOS_ONLY_NATIVE_MODULE = ` +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +import type {TurboModule} from '../RCTExport'; +import * as TurboModuleRegistry from '../TurboModuleRegistry'; + +export interface Spec extends TurboModule { + // no methods +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModuleIOS'); + +`; + module.exports = { NATIVE_MODULE_WITH_OBJECT_WITH_OBJECT_DEFINED_IN_FILE_AS_PROPERTY, NATIVE_MODULE_WITH_ARRAY_WITH_UNION_AND_TOUPLE, @@ -515,4 +563,6 @@ module.exports = { NATIVE_MODULE_WITH_BASIC_PARAM_TYPES, NATIVE_MODULE_WITH_CALLBACK, EMPTY_NATIVE_MODULE, + ANDROID_ONLY_NATIVE_MODULE, + IOS_ONLY_NATIVE_MODULE, }; diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap index 9c38dc8fbfe..a8aa9c50d73 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap +++ b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap @@ -16,6 +16,26 @@ exports[`RN Codegen Flow Parser Fails with error message TWO_NATIVE_EXTENDING_TU exports[`RN Codegen Flow Parser Fails with error message TWO_NATIVE_MODULES_EXPORTED_WITH_DEFAULT 1`] = `"File neither contains a module declaration, nor a component declaration. For module declarations, please make sure your file has an InterfaceDeclaration extending TurboModule. For component declarations, please make sure your file has a default export calling the codegenNativeComponent(...) macro."`; +exports[`RN Codegen Flow Parser can generate fixture ANDROID_ONLY_NATIVE_MODULE 1`] = ` +"{ + 'modules': { + 'NativeSampleTurboModule': { + 'type': 'NativeModule', + 'aliases': {}, + 'spec': { + 'properties': [] + }, + 'moduleNames': [ + 'SampleTurboModuleAndroid' + ], + 'excludedPlatforms': [ + 'iOS' + ] + } + } +}" +`; + exports[`RN Codegen Flow Parser can generate fixture EMPTY_NATIVE_MODULE 1`] = ` "{ 'modules': { @@ -33,6 +53,26 @@ exports[`RN Codegen Flow Parser can generate fixture EMPTY_NATIVE_MODULE 1`] = ` }" `; +exports[`RN Codegen Flow Parser can generate fixture IOS_ONLY_NATIVE_MODULE 1`] = ` +"{ + 'modules': { + 'NativeSampleTurboModule': { + 'type': 'NativeModule', + 'aliases': {}, + 'spec': { + 'properties': [] + }, + 'moduleNames': [ + 'SampleTurboModuleIOS' + ], + 'excludedPlatforms': [ + 'android' + ] + } + } +}" +`; + exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_ALIASES 1`] = ` "{ 'modules': { diff --git a/packages/react-native-codegen/src/parsers/flow/modules/index.js b/packages/react-native-codegen/src/parsers/flow/modules/index.js index 652ac9f6180..7412e901375 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -363,7 +363,7 @@ function translateFunctionTypeAnnotation( } function buildPropertySchema( - moduleName: string, + hasteModuleName: string, // TODO(T71778680): This is an ObjectTypeProperty containing either: // - a FunctionTypeAnnotation or GenericTypeAnnotation // - a NullableTypeAnnoation containing a FunctionTypeAnnotation or GenericTypeAnnotation @@ -390,13 +390,13 @@ function buildPropertySchema( optional: property.optional, typeAnnotation: wrapNullable( nullable, - translateFunctionTypeAnnotation(moduleName, value, types, aliasMap), + translateFunctionTypeAnnotation(hasteModuleName, value, types, aliasMap), ), }; } function buildModuleSchema( - moduleName: string, + hasteModuleName: string, moduleNames: $ReadOnlyArray, types: TypeDeclarationMap, ): NativeModuleSchema { @@ -424,6 +424,20 @@ function buildModuleSchema( "Nativemodule interface must be called 'Spec'", ); + // 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 excludedPlatforms = []; + const namesToValidate = [...moduleNames, hasteModuleName]; + namesToValidate.forEach(name => { + if (name.endsWith('Android')) { + excludedPlatforms.push('iOS'); + } else if (name.endsWith('IOS')) { + excludedPlatforms.push('android'); + } + }); + const declaration = types[moduleInterfaceName]; return (declaration.body.properties: $ReadOnlyArray<$FlowFixMe>) .filter(property => property.type === 'ObjectTypeProperty') @@ -432,7 +446,7 @@ function buildModuleSchema( return { aliasMap: aliasMap, propertySchema: buildPropertySchema( - moduleName, + hasteModuleName, property, types, aliasMap, @@ -448,6 +462,7 @@ function buildModuleSchema( properties: [...moduleSchema.spec.properties, propertySchema], }, moduleNames: moduleSchema.moduleNames, + excludedPlatforms: moduleSchema.excludedPlatforms, }; }, { @@ -455,6 +470,8 @@ function buildModuleSchema( aliases: {}, spec: {properties: []}, moduleNames: moduleNames, + excludedPlatforms: + excludedPlatforms.length !== 0 ? [...excludedPlatforms] : undefined, }, ); }