From 3cd97e4994b319f77c7afbf3a5dad5095b6ea9f3 Mon Sep 17 00:00:00 2001 From: Tarun Chauhan Date: Fri, 10 Mar 2023 07:58:32 -0800 Subject: [PATCH] extract buildModuleSchema to parsers-commons (#36330) Summary: > Extract the buildModuleSchema function ([Flow](https://github.com/facebook/react-native/blob/main/packages/react-native-codegen/src/parsers/flow/modules/index.js#L571), [TypeScript](https://github.com/facebook/react-native/blob/main/packages/react-native-codegen/src/parsers/typescript/modules/index.js#L584))in the parsers-commons.js function. The two functions are almost identical except for the filter(property =>) at the end of the function, which is different based on the language. Part of Codegen Issue https://github.com/facebook/react-native/issues/34872 Depends on Codegen 74, Codegen 84. ## Changelog [INTERNAL] [CHANGED] - Extract buildModuleSchema from Flow and TypeScript parsers modules to parsers-commons Pull Request resolved: https://github.com/facebook/react-native/pull/36330 Test Plan: `yarn lint && yarn run flow && yarn test react-native-codegen ` Reviewed By: christophpurrer Differential Revision: D43833653 Pulled By: cipolleschi fbshipit-source-id: 4d67cb5ef746ecd7ace4b91eb30d36e96252e779 --- .../react-native-modules.js | 38 ++- .../parsers/__tests__/parsers-commons-test.js | 232 +++++++++++++++++- .../src/parsers/flow/modules/index.js | 113 --------- .../src/parsers/flow/parser.js | 5 +- .../src/parsers/parsers-commons.js | 135 +++++++++- .../src/parsers/typescript/modules/index.js | 124 +--------- .../src/parsers/typescript/parser.js | 5 +- 7 files changed, 402 insertions(+), 250 deletions(-) diff --git a/packages/eslint-plugin-specs/react-native-modules.js b/packages/eslint-plugin-specs/react-native-modules.js index 8a8a037ef0001a..816ab33f2ab677 100644 --- a/packages/eslint-plugin-specs/react-native-modules.js +++ b/packages/eslint-plugin-specs/react-native-modules.js @@ -24,9 +24,17 @@ const ERRORS = { let RNModuleParser; let RNParserUtils; let RNFlowParser; +let RNParserCommons; +let RNFlowParserUtils; function requireModuleParser() { - if (RNModuleParser == null || RNParserUtils == null || RNFlowParser == null) { + if ( + RNModuleParser == null || + RNParserUtils == null || + RNFlowParser == null || + RNParserCommons == null || + RNFlowParserUtils == null + ) { // If using this externally, we leverage @react-native/codegen as published form if (!PACKAGE_USAGE) { const config = { @@ -38,6 +46,8 @@ function requireModuleParser() { RNModuleParser = require('@react-native/codegen/src/parsers/flow/modules'); RNParserUtils = require('@react-native/codegen/src/parsers/utils'); RNFlowParser = require('@react-native/codegen/src/parsers/flow/parser'); + RNParserCommons = require('@react-native/codegen/src/parsers/parsers-commons'); + RNFlowParserUtils = require('@react-native/codegen/src/parsers/flow/utils'); }); } else { const config = { @@ -47,16 +57,20 @@ function requireModuleParser() { withBabelRegister(config, () => { RNModuleParser = require('@react-native/codegen/lib/parsers/flow/modules'); - RNParserUtils = require('@react-native/codegen/lib/parsers/flow/utils'); + RNParserUtils = require('@react-native/codegen/lib/parsers/utils'); RNFlowParser = require('@react-native/codegen/lib/parsers/flow/parser'); + RNParserCommons = require('@react-native/codegen/lib/parsers/parsers-commons'); + RNFlowParserUtils = require('@react-native/codegen/lib/parsers/flow/utils'); }); } } return { - buildModuleSchema: RNModuleParser.buildModuleSchema, + buildModuleSchema: RNParserCommons.buildModuleSchema, createParserErrorCapturer: RNParserUtils.createParserErrorCapturer, parser: new RNFlowParser.FlowParser(), + resolveTypeAnnotation: RNFlowParserUtils.resolveTypeAnnotation, + translateTypeAnnotation: RNModuleParser.flowTranslateTypeAnnotation, }; } @@ -131,8 +145,13 @@ function rule(context) { }); } - const {buildModuleSchema, createParserErrorCapturer, parser} = - requireModuleParser(); + const { + buildModuleSchema, + createParserErrorCapturer, + parser, + resolveTypeAnnotation, + translateTypeAnnotation, + } = requireModuleParser(); const [parsingErrors, tryParse] = createParserErrorCapturer(); @@ -140,7 +159,14 @@ function rule(context) { const ast = parser.getAst(sourceCode); tryParse(() => { - buildModuleSchema(hasteModuleName, ast, tryParse, parser); + buildModuleSchema( + hasteModuleName, + ast, + tryParse, + parser, + resolveTypeAnnotation, + translateTypeAnnotation, + ); }); parsingErrors.forEach(error => { diff --git a/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js b/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js index 86ed3fab3995e7..7ab1c5d0787bdc 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js @@ -26,8 +26,11 @@ import type {ParserType} from '../errors'; const {Visitor} = require('../flow/Visitor'); const {wrapComponentSchema} = require('../schema.js'); const {buildComponentSchema} = require('../flow/components'); -const {buildModuleSchema} = require('../flow/modules'); -const {isModuleRegistryCall} = require('../utils.js'); +const {buildModuleSchema} = require('../parsers-commons.js'); +const { + isModuleRegistryCall, + createParserErrorCapturer, +} = require('../utils.js'); const { ParserError, UnsupportedObjectPropertyTypeAnnotationParserError, @@ -36,6 +39,9 @@ const { IncorrectModuleRegistryCallArityParserError, IncorrectModuleRegistryCallArgumentTypeParserError, UntypedModuleRegistryCallParserError, + ModuleInterfaceNotFoundParserError, + MoreThanOneModuleInterfaceParserError, + MisnamedModuleInterfaceParserError, } = require('../errors'); import {MockedParser} from '../parserMock'; @@ -45,8 +51,9 @@ const parser = new MockedParser(); const flowParser = new FlowParser(); -const flowTranslateTypeAnnotation = require('../flow/modules/index'); +const {flowTranslateTypeAnnotation} = require('../flow/modules/index'); const typeScriptTranslateTypeAnnotation = require('../typescript/modules/index'); +const {resolveTypeAnnotation} = require('../flow/utils'); beforeEach(() => { jest.clearAllMocks(); @@ -396,7 +403,9 @@ describe('buildSchemaFromConfigType', () => { const buildComponentSchemaMock = jest.fn( (_ast, _parser) => componentSchemaMock, ); - const buildModuleSchemaMock = jest.fn((_0, _1, _2, _3) => moduleSchemaMock); + const buildModuleSchemaMock = jest.fn( + (_0, _1, _2, _3, _4, _5) => moduleSchemaMock, + ); const buildSchemaFromConfigTypeHelper = ( configType: 'module' | 'component' | 'none', @@ -410,6 +419,8 @@ describe('buildSchemaFromConfigType', () => { buildComponentSchemaMock, buildModuleSchemaMock, parser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, ); describe('when configType is none', () => { @@ -491,6 +502,8 @@ describe('buildSchemaFromConfigType', () => { astMock, expect.any(Function), parser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, ); expect(buildComponentSchemaMock).not.toHaveBeenCalled(); @@ -661,6 +674,8 @@ describe('buildSchema', () => { buildModuleSchema, Visitor, parser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, ); expect(getConfigTypeSpy).not.toHaveBeenCalled(); @@ -693,6 +708,8 @@ describe('buildSchema', () => { buildModuleSchema, Visitor, flowParser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, ); expect(getConfigTypeSpy).toHaveBeenCalledTimes(1); @@ -746,6 +763,8 @@ describe('buildSchema', () => { buildModuleSchema, Visitor, flowParser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, ); expect(getConfigTypeSpy).toHaveBeenCalledTimes(1); @@ -1007,3 +1026,208 @@ describe('parseModuleName', () => { }); }); }); + +describe('buildModuleSchema', () => { + const hasteModuleName = 'TestModuleName'; + const [, tryParse] = createParserErrorCapturer(); + const language = flowParser.language(); + const NATIVE_MODULE = ` + import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; + import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; + + export interface Spec extends TurboModule { + +getArray: (a: Array) => Array; + } + + export default (TurboModuleRegistry.getEnforcing( + 'SampleTurboModule', + ): Spec); + `; + + describe('throwIfModuleInterfaceNotFound', () => { + it('should throw ModuleInterfaceNotFoundParserError if no module interface is found', () => { + const ast = flowParser.getAst(''); + const expected = new ModuleInterfaceNotFoundParserError( + hasteModuleName, + ast, + language, + ); + + expect(() => + buildModuleSchema( + hasteModuleName, + ast, + tryParse, + flowParser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, + ), + ).toThrow(expected); + }); + + it('should not throw ModuleInterfaceNotFoundParserError if module interface is found', () => { + const ast = flowParser.getAst(NATIVE_MODULE); + + expect(() => + buildModuleSchema( + hasteModuleName, + ast, + tryParse, + flowParser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, + ), + ).not.toThrow(); + }); + }); + + describe('throwIfMoreThanOneModuleInterfaceParser', () => { + it('should throw an error if mulitple module interfaces are found', () => { + const contents = ` + import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; + import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; + + export interface Spec extends TurboModule { + +getBool: (arg: boolean) => boolean; } + export interface SpecOther extends TurboModule { + +getArray: (a: Array) => Array; + } + `; + const ast = flowParser.getAst(contents); + const types = flowParser.getTypes(ast); + const moduleSpecs = Object.values(types).filter(t => + flowParser.isModuleInterface(t), + ); + const expected = new MoreThanOneModuleInterfaceParserError( + hasteModuleName, + moduleSpecs, + moduleSpecs.map(node => node.id.name), + language, + ); + + expect(() => + buildModuleSchema( + hasteModuleName, + ast, + tryParse, + flowParser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, + ), + ).toThrow(expected); + }); + + it('should not throw an error if exactly one module interface is found', () => { + const ast = flowParser.getAst(NATIVE_MODULE); + + expect(() => + buildModuleSchema( + hasteModuleName, + ast, + tryParse, + flowParser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, + ), + ).not.toThrow(); + }); + }); + + describe('throwIfModuleInterfaceIsMisnamed', () => { + it('should throw an error if module interface is misnamed', () => { + const contents = ` + import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; + import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; + + export interface MisnamedSpec extends TurboModule { + +getArray: (a: Array) => Array; + } + + export default (TurboModuleRegistry.getEnforcing( + 'SampleTurboModule', + ): Spec); + `; + const ast = flowParser.getAst(contents); + const types = flowParser.getTypes(ast); + const moduleSpecs = Object.values(types).filter(t => + flowParser.isModuleInterface(t), + ); + const [moduleSpec] = moduleSpecs; + + const expected = new MisnamedModuleInterfaceParserError( + hasteModuleName, + moduleSpec.id, + language, + ); + + expect(() => + buildModuleSchema( + hasteModuleName, + ast, + tryParse, + flowParser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, + ), + ).toThrow(expected); + }); + + it('should not throw an error if module interface is correctly named', () => { + const ast = flowParser.getAst(NATIVE_MODULE); + + expect(() => + buildModuleSchema( + hasteModuleName, + ast, + tryParse, + flowParser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, + ), + ).not.toThrow(); + }); + }); + + it('should return valid module schema', () => { + const ast = flowParser.getAst(NATIVE_MODULE); + const schmeaMock = { + aliasMap: {}, + enumMap: {}, + excludedPlatforms: undefined, + moduleName: 'SampleTurboModule', + spec: { + properties: [ + { + name: 'getArray', + optional: false, + typeAnnotation: { + params: [ + { + name: 'a', + optional: false, + typeAnnotation: {type: 'ArrayTypeAnnotation'}, + }, + ], + returnTypeAnnotation: { + elementType: {type: 'StringTypeAnnotation'}, + type: 'ArrayTypeAnnotation', + }, + type: 'FunctionTypeAnnotation', + }, + }, + ], + }, + type: 'NativeModule', + }; + const schema = buildModuleSchema( + hasteModuleName, + ast, + tryParse, + flowParser, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, + ); + + expect(schema).toEqual(schmeaMock); + }); +}); 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 b6870c3674c970..b76ebec34dab1e 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -16,23 +16,18 @@ import type { NativeModuleEnumMap, NativeModuleBaseTypeAnnotation, NativeModuleTypeAnnotation, - NativeModulePropertyShape, - NativeModuleSchema, Nullable, } from '../../../CodegenSchema'; import type {Parser} from '../../parser'; import type {ParserErrorCapturer, TypeDeclarationMap} from '../../utils'; -const {verifyPlatforms} = require('../../utils'); const {resolveTypeAnnotation} = require('../utils'); const { unwrapNullable, wrapNullable, assertGenericTypeAnnotationHasExactlyOneTypeParameter, parseObjectProperty, - buildPropertySchema, - parseModuleName, } = require('../../parsers-commons'); const { emitArrayType, @@ -61,9 +56,6 @@ const { } = require('../../errors'); const { - throwIfModuleInterfaceNotFound, - throwIfModuleInterfaceIsMisnamed, - throwIfMoreThanOneModuleInterfaceParserError, throwIfPartialNotAnnotatingTypeParameter, throwIfPartialWithMoreParameter, } = require('../../error-utils'); @@ -318,111 +310,6 @@ function translateTypeAnnotation( } } -function buildModuleSchema( - hasteModuleName: string, - /** - * TODO(T71778680): Flow-type this node. - */ - ast: $FlowFixMe, - tryParse: ParserErrorCapturer, - parser: Parser, -): NativeModuleSchema { - const types = parser.getTypes(ast); - const moduleSpecs = (Object.values(types): $ReadOnlyArray<$FlowFixMe>).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 - // Also checks and throws error if: - // - Module Interface is Unused - // - More than 1 Module Registry Calls - // - Wrong number of Call Expression Args - // - Module Registry Call Args are Incorrect - // - Module is Untyped - // - Module Registry Call Type Parameter is Icorrect - 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, - ); - - // $FlowFixMe[missing-type-arg] - return (moduleSpec.body.properties: $ReadOnlyArray<$FlowFixMe>) - .filter(property => property.type === 'ObjectTypeProperty') - .map(property => { - const aliasMap: {...NativeModuleAliasMap} = {}; - const enumMap: {...NativeModuleEnumMap} = {}; - return tryParse(() => ({ - aliasMap: aliasMap, - enumMap: enumMap, - propertyShape: buildPropertySchema( - hasteModuleName, - property, - types, - aliasMap, - enumMap, - tryParse, - cxxOnly, - resolveTypeAnnotation, - translateTypeAnnotation, - parser, - ), - })); - }) - .filter(Boolean) - .reduce( - ( - moduleSchema: NativeModuleSchema, - {aliasMap, enumMap, propertyShape}, - ) => ({ - type: 'NativeModule', - aliasMap: {...moduleSchema.aliasMap, ...aliasMap}, - enumMap: {...moduleSchema.enumMap, ...enumMap}, - spec: { - properties: [...moduleSchema.spec.properties, propertyShape], - }, - moduleName: moduleSchema.moduleName, - excludedPlatforms: moduleSchema.excludedPlatforms, - }), - { - type: 'NativeModule', - aliasMap: {}, - enumMap: {}, - spec: {properties: []}, - moduleName, - excludedPlatforms: - excludedPlatforms.length !== 0 ? [...excludedPlatforms] : undefined, - }, - ); -} - module.exports = { - buildModuleSchema, flowTranslateTypeAnnotation: translateTypeAnnotation, }; diff --git a/packages/react-native-codegen/src/parsers/flow/parser.js b/packages/react-native-codegen/src/parsers/flow/parser.js index d57d60e6a9ab99..eabcfa3d7b45bf 100644 --- a/packages/react-native-codegen/src/parsers/flow/parser.js +++ b/packages/react-native-codegen/src/parsers/flow/parser.js @@ -34,7 +34,8 @@ const {buildSchema} = require('../parsers-commons'); const {Visitor} = require('./Visitor'); const {buildComponentSchema} = require('./components'); const {wrapComponentSchema} = require('../schema.js'); -const {buildModuleSchema} = require('./modules'); +const {buildModuleSchema} = require('../parsers-commons.js'); +const {resolveTypeAnnotation} = require('./utils'); const fs = require('fs'); @@ -105,6 +106,8 @@ class FlowParser implements Parser { buildModuleSchema, Visitor, this, + resolveTypeAnnotation, + flowTranslateTypeAnnotation, ); } diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index b2285537b0f7d9..ae073d8c21136c 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -21,6 +21,7 @@ import type { NativeModuleParamTypeAnnotation, NativeModulePropertyShape, SchemaType, + NativeModuleEnumMap, } from '../CodegenSchema.js'; import type {Parser} from './parser'; @@ -34,6 +35,7 @@ const { createParserErrorCapturer, visit, isModuleRegistryCall, + verifyPlatforms, } = require('./utils'); const { throwIfPropertyValueTypeIsUnsupported, @@ -46,6 +48,9 @@ const { throwIfUntypedModule, throwIfIncorrectModuleRegistryCallTypeParameterParserError, throwIfIncorrectModuleRegistryCallArgument, + throwIfModuleInterfaceNotFound, + throwIfMoreThanOneModuleInterfaceParserError, + throwIfModuleInterfaceIsMisnamed, } = require('./error-utils'); const { @@ -55,7 +60,6 @@ const { } = require('./errors'); const invariant = require('invariant'); -import type {NativeModuleEnumMap} from '../CodegenSchema'; function wrapModuleSchema( nativeModuleSchema: NativeModuleSchema, @@ -361,8 +365,12 @@ function buildSchemaFromConfigType( ast: $FlowFixMe, tryParse: ParserErrorCapturer, parser: Parser, + resolveTypeAnnotation: $FlowFixMe, + translateTypeAnnotation: $FlowFixMe, ) => NativeModuleSchema, parser: Parser, + resolveTypeAnnotation: $FlowFixMe, + translateTypeAnnotation: $FlowFixMe, ): SchemaType { switch (configType) { case 'component': { @@ -377,7 +385,14 @@ function buildSchemaFromConfigType( const [parsingErrors, tryParse] = createParserErrorCapturer(); const schema = tryParse(() => - buildModuleSchema(nativeModuleName, ast, tryParse, parser), + buildModuleSchema( + nativeModuleName, + ast, + tryParse, + parser, + resolveTypeAnnotation, + translateTypeAnnotation, + ), ); if (parsingErrors.length > 0) { @@ -417,11 +432,15 @@ function buildSchema( ast: $FlowFixMe, tryParse: ParserErrorCapturer, parser: Parser, + resolveTypeAnnotation: $FlowFixMe, + translateTypeAnnotation: $FlowFixMe, ) => NativeModuleSchema, Visitor: ({isComponent: boolean, isModule: boolean}) => { [type: string]: (node: $FlowFixMe) => void, }, parser: Parser, + resolveTypeAnnotation: $FlowFixMe, + translateTypeAnnotation: $FlowFixMe, ): SchemaType { // Early return for non-Spec JavaScript files if ( @@ -442,6 +461,8 @@ function buildSchema( buildComponentSchema, buildModuleSchema, parser, + resolveTypeAnnotation, + translateTypeAnnotation, ); } @@ -510,6 +531,115 @@ const parseModuleName = ( return $moduleName; }; +const buildModuleSchema = ( + hasteModuleName: string, + /** + * TODO(T71778680): Flow-type this node. + */ + ast: $FlowFixMe, + tryParse: ParserErrorCapturer, + parser: Parser, + resolveTypeAnnotation: $FlowFixMe, + translateTypeAnnotation: $FlowFixMe, +): NativeModuleSchema => { + const language = parser.language(); + const types = parser.getTypes(ast); + const moduleSpecs = (Object.values(types): $ReadOnlyArray<$FlowFixMe>).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 properties: $ReadOnlyArray<$FlowFixMe> = + language === 'Flow' ? moduleSpec.body.properties : moduleSpec.body.body; + + // $FlowFixMe[missing-type-arg] + return properties + .filter( + property => + property.type === 'ObjectTypeProperty' || + property.type === 'TSPropertySignature' || + property.type === 'TSMethodSignature', + ) + .map(property => { + const aliasMap: {...NativeModuleAliasMap} = {}; + const enumMap: {...NativeModuleEnumMap} = {}; + + return tryParse(() => ({ + aliasMap, + enumMap, + propertyShape: buildPropertySchema( + hasteModuleName, + property, + types, + aliasMap, + enumMap, + tryParse, + cxxOnly, + resolveTypeAnnotation, + translateTypeAnnotation, + parser, + ), + })); + }) + .filter(Boolean) + .reduce( + ( + moduleSchema: NativeModuleSchema, + {aliasMap, enumMap, propertyShape}, + ) => ({ + type: 'NativeModule', + aliasMap: {...moduleSchema.aliasMap, ...aliasMap}, + enumMap: {...moduleSchema.enumMap, ...enumMap}, + spec: { + properties: [...moduleSchema.spec.properties, propertyShape], + }, + moduleName: moduleSchema.moduleName, + excludedPlatforms: moduleSchema.excludedPlatforms, + }), + { + type: 'NativeModule', + aliasMap: {}, + enumMap: {}, + spec: {properties: []}, + moduleName, + excludedPlatforms: + excludedPlatforms.length !== 0 ? [...excludedPlatforms] : undefined, + }, + ); +}; + module.exports = { wrapModuleSchema, unwrapNullable, @@ -522,4 +652,5 @@ module.exports = { buildSchemaFromConfigType, buildSchema, parseModuleName, + buildModuleSchema, }; diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/index.js b/packages/react-native-codegen/src/parsers/typescript/modules/index.js index 94aa7a3111b2b9..5208ed333ee78d 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -15,9 +15,7 @@ import type { NativeModuleAliasMap, NativeModuleEnumMap, NativeModuleBaseTypeAnnotation, - NativeModulePropertyShape, NativeModuleTypeAnnotation, - NativeModuleSchema, Nullable, } from '../../../CodegenSchema'; @@ -30,14 +28,9 @@ import type { const {flattenIntersectionType} = require('../parseTopLevelType'); const {flattenProperties} = require('../components/componentsUtils'); -const {verifyPlatforms} = require('../../utils'); const {resolveTypeAnnotation} = require('../utils'); -const { - parseObjectProperty, - buildPropertySchema, - parseModuleName, -} = require('../../parsers-commons'); +const {parseObjectProperty} = require('../../parsers-commons'); const {typeEnumResolution} = require('../../parsers-primitives'); const { @@ -67,9 +60,6 @@ const { } = require('../../errors'); const { - throwIfModuleInterfaceNotFound, - throwIfModuleInterfaceIsMisnamed, - throwIfMoreThanOneModuleInterfaceParserError, throwIfPartialNotAnnotatingTypeParameter, throwIfPartialWithMoreParameter, } = require('../../error-utils'); @@ -422,118 +412,6 @@ function translateTypeAnnotation( } } -function buildModuleSchema( - hasteModuleName: string, - /** - * TODO(T108222691): Use flow-types for @babel/parser - */ - ast: $FlowFixMe, - tryParse: ParserErrorCapturer, - parser: Parser, -): NativeModuleSchema { - const types = parser.getTypes(ast); - const moduleSpecs = (Object.values(types): $ReadOnlyArray<$FlowFixMe>).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 - // Also checks and throws error if: - // - Module Interface is Unused - // - More than 1 Module Registry Calls - // - Wrong number of Call Expression Args - // - Module Registry Call Args are Incorrect - // - Module is Untyped - // - Module Registry Call Type Parameter is Icorrect - 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, - ); - - // $FlowFixMe[missing-type-arg] - return (moduleSpec.body.body: $ReadOnlyArray<$FlowFixMe>) - .filter( - property => - property.type === 'TSMethodSignature' || - property.type === 'TSPropertySignature', - ) - .map(property => { - const aliasMap: {...NativeModuleAliasMap} = {}; - const enumMap: {...NativeModuleEnumMap} = {}; - - return tryParse(() => ({ - aliasMap: aliasMap, - enumMap: enumMap, - propertyShape: buildPropertySchema( - hasteModuleName, - property, - types, - aliasMap, - enumMap, - tryParse, - cxxOnly, - resolveTypeAnnotation, - translateTypeAnnotation, - parser, - ), - })); - }) - .filter(Boolean) - .reduce( - ( - moduleSchema: NativeModuleSchema, - {aliasMap, enumMap, propertyShape}, - ) => { - return { - type: 'NativeModule', - aliasMap: {...moduleSchema.aliasMap, ...aliasMap}, - enumMap: {...moduleSchema.enumMap, ...enumMap}, - spec: { - properties: [...moduleSchema.spec.properties, propertyShape], - }, - moduleName: moduleSchema.moduleName, - excludedPlatforms: moduleSchema.excludedPlatforms, - }; - }, - { - type: 'NativeModule', - aliasMap: {}, - enumMap: {}, - spec: {properties: []}, - moduleName: moduleName, - excludedPlatforms: - excludedPlatforms.length !== 0 ? [...excludedPlatforms] : undefined, - }, - ); -} - module.exports = { - buildModuleSchema, typeScriptTranslateTypeAnnotation: translateTypeAnnotation, }; diff --git a/packages/react-native-codegen/src/parsers/typescript/parser.js b/packages/react-native-codegen/src/parsers/typescript/parser.js index 7e120fcd85fb88..04d7725caf041f 100644 --- a/packages/react-native-codegen/src/parsers/typescript/parser.js +++ b/packages/react-native-codegen/src/parsers/typescript/parser.js @@ -34,7 +34,8 @@ const {buildSchema} = require('../parsers-commons'); const {Visitor} = require('./Visitor'); const {buildComponentSchema} = require('./components'); const {wrapComponentSchema} = require('../schema.js'); -const {buildModuleSchema} = require('./modules'); +const {buildModuleSchema} = require('../parsers-commons.js'); +const {resolveTypeAnnotation} = require('./utils'); const fs = require('fs'); @@ -107,6 +108,8 @@ class TypeScriptParser implements Parser { buildModuleSchema, Visitor, this, + resolveTypeAnnotation, + typeScriptTranslateTypeAnnotation, ); }