diff --git a/packages/react-native-codegen/src/CodegenSchema.js b/packages/react-native-codegen/src/CodegenSchema.js index 4b39883813c..4450d5a1ef3 100644 --- a/packages/react-native-codegen/src/CodegenSchema.js +++ b/packages/react-native-codegen/src/CodegenSchema.js @@ -128,6 +128,12 @@ export type PropTypeShape = $ReadOnly<{| typeAnnotation: PropTypeTypeAnnotation, |}>; +export type MethodTypeShape = $ReadOnly<{||}>; + +export type NativeModuleShape = $ReadOnly<{| + properties: $ReadOnlyArray, +|}>; + export type EventTypeShape = $ReadOnly<{| name: string, bubblingType: 'direct' | 'bubble', @@ -179,6 +185,9 @@ export type SchemaType = $ReadOnly<{| components?: $ReadOnly<{ [component: string]: ComponentShape, }>, + nativeModules?: $ReadOnly<{ + [nativeModule: string]: NativeModuleShape, + }>, |}>, }>, |}>; diff --git a/packages/react-native-codegen/src/parsers/flow/__test_fixtures__/failures.js b/packages/react-native-codegen/src/parsers/flow/__test_fixtures__/failures.js index 88681a062d5..99d07142608 100644 --- a/packages/react-native-codegen/src/parsers/flow/__test_fixtures__/failures.js +++ b/packages/react-native-codegen/src/parsers/flow/__test_fixtures__/failures.js @@ -10,6 +10,52 @@ 'use strict'; +const INCORRECT_NATIVE_MODULES = ` +/** + * 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 + * @format + */ + +'use strict'; + +import type {TurboModule} from '../RCTExport'; +import * as TurboModuleRegistry from '../TurboModuleRegistry'; + +export interface SpecWithoutTypo extends TurboModule { + // no methods +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); + +`; + +const TWO_NATIVE_MODULES_EXPORTED_WITH_DEFAULT = ` +/** + * 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 + * @format + */ + +'use strict'; + +import type {TurboModule} from '../RCTExport'; +import * as TurboModuleRegistry from '../TurboModuleRegistry'; + + +export default TurboModuleRegistry.getEnforcing('SampleTurboModule1'); +export default TurboModuleRegistry.getEnforcing('SampleTurboModule2'); + +`; + const COMMANDS_DEFINED_INLINE = ` /** * Copyright (c) Facebook, Inc. and its affiliates. @@ -154,6 +200,8 @@ export default codegenNativeComponent('Module'); `; module.exports = { + TWO_NATIVE_MODULES_EXPORTED_WITH_DEFAULT, + INCORRECT_NATIVE_MODULES, COMMANDS_DEFINED_INLINE, COMMANDS_DEFINED_MULTIPLE_TIMES, COMMANDS_DEFINED_WITHOUT_REF, diff --git a/packages/react-native-codegen/src/parsers/flow/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/flow/__test_fixtures__/fixtures.js index 617f7039fdc..210da65cbd5 100644 --- a/packages/react-native-codegen/src/parsers/flow/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/flow/__test_fixtures__/fixtures.js @@ -10,6 +10,30 @@ 'use strict'; +const EMPTY_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 + * @format + */ + +'use strict'; + +import type {TurboModule} from '../RCTExport'; +import * as TurboModuleRegistry from '../TurboModuleRegistry'; + +export interface Spec extends TurboModule { + // mo methods +} + +export default TurboModuleRegistry.getEnforcing('SampleTurboModule'); + +`; + const EVENT_DEFINITION = ` boolean_required: boolean, boolean_optional_key?: boolean, @@ -550,6 +574,7 @@ export default codegenNativeComponent('Module'); `; module.exports = { + EMPTY_NATIVE_MODULE, ALL_PROP_TYPES_NO_EVENTS, ARRAY_PROP_TYPES_NO_EVENTS, ONE_OF_EACH_PROP_EVENT_DEFAULT_AND_OPTIONS, diff --git a/packages/react-native-codegen/src/parsers/flow/__tests__/__snapshots__/parser-test.js.snap b/packages/react-native-codegen/src/parsers/flow/__tests__/__snapshots__/parser-test.js.snap index 2171259177a..e4954350844 100644 --- a/packages/react-native-codegen/src/parsers/flow/__tests__/__snapshots__/parser-test.js.snap +++ b/packages/react-native-codegen/src/parsers/flow/__tests__/__snapshots__/parser-test.js.snap @@ -6,8 +6,12 @@ exports[`RN Codegen Flow Parser Fails with error message COMMANDS_DEFINED_MULTIP exports[`RN Codegen Flow Parser Fails with error message COMMANDS_DEFINED_WITHOUT_REF 1`] = `"The first argument of method hotspotUpdate must be of type React.Ref<>"`; +exports[`RN Codegen Flow Parser Fails with error message INCORRECT_NATIVE_MODULES 1`] = `"Interface properties for \\"SpecWithTypo has been specified incorrectly.\\""`; + exports[`RN Codegen Flow Parser Fails with error message NULLABLE_WITH_DEFAULT 1`] = `"WithDefault<> is optional and does not need to be marked as optional. Please remove the ? annotation in front of it."`; +exports[`RN Codegen Flow Parser Fails with error message TWO_NATIVE_MODULES_EXPORTED_WITH_DEFAULT 1`] = `"File should contain only one default export."`; + exports[`RN Codegen Flow Parser can generate fixture ALL_PROP_TYPES_NO_EVENTS 1`] = ` Object { "modules": Object { @@ -856,6 +860,20 @@ Object { } `; +exports[`RN Codegen Flow Parser can generate fixture EMPTY_NATIVE_MODULE 1`] = ` +Object { + "modules": Object { + "SampleTurboModule": Object { + "nativeModules": Object { + "SampleTurboModule": Object { + "properties": Array [], + }, + }, + }, + }, +} +`; + exports[`RN Codegen Flow Parser can generate fixture EVENTS_DEFINED_AS_NULL_INLINE 1`] = ` Object { "modules": Object { diff --git a/packages/react-native-codegen/src/parsers/flow/index.js b/packages/react-native-codegen/src/parsers/flow/index.js index 3d73e86383b..14c689d9882 100644 --- a/packages/react-native-codegen/src/parsers/flow/index.js +++ b/packages/react-native-codegen/src/parsers/flow/index.js @@ -11,6 +11,7 @@ 'use strict'; import type {SchemaType} from '../../CodegenSchema.js'; +import type {SchemaBuilderConfig} from './schema.js'; // $FlowFixMe there's no flowtype flow-parser const flowParser = require('flow-parser'); const fs = require('fs'); @@ -20,8 +21,9 @@ const {getEvents} = require('./events'); const {getProps} = require('./props'); const {getOptions} = require('./options'); const {getExtendsProps} = require('./extends'); +const {getMethods} = require('./methods'); -function findConfig(ast) { +function findComponentConfig(ast) { const foundConfigs = []; const defaultExports = ast.body.filter( @@ -148,36 +150,99 @@ function getCommandProperties(commandTypeName, types) { } } -function processString(contents: string) { +function getConfigType(ast): 'module' | 'component' { + const defaultExports = ast.body.filter( + node => node.type === 'ExportDefaultDeclaration', + ); + if (defaultExports.length !== 1) { + throw new Error('File should contain only one default export.'); + } + if (defaultExports[0].declaration && defaultExports[0].declaration.callee) { + const statement = defaultExports[0].declaration.callee; + if (statement.name === 'codegenNativeComponent') { + return 'component'; + } + if (statement.object && statement.object.name === 'TurboModuleRegistry') { + return 'module'; + } + } + throw new Error( + `Default export for module specified incorrectly. It should containts + either "TurboModuleRegistry.getEnforcing" or "codegenNativeComponent".`, + ); +} + +function getModuleProperties(types, interfaceName) { + if (types[interfaceName] && types[interfaceName].body) { + return types[interfaceName].body.properties; + } + throw new Error( + `Interface properties for "${interfaceName} has been specified incorrectly."`, + ); +} + +function findModuleConfig( + ast, +): $ReadOnly<{|moduleName: string, interfaceName: string|}> { + const defaultExport = ast.body.filter( + node => node.type === 'ExportDefaultDeclaration', + )[0]; + try { + const interfaceName = + defaultExport.declaration.typeArguments.params[0].id.name; + + const moduleName = defaultExport.declaration.arguments[0].value; + return {interfaceName, moduleName}; + } catch (e) { + throw new Error( + `Default export for module specified incorrectly. It should containts + either "TurboModuleRegistry.getEnforcing" or "codegenNativeComponent".`, + ); + } +} + +function processString(contents: string): SchemaBuilderConfig { const ast = flowParser.parse(contents); const types = getTypes(ast); - const { - componentName, - propsTypeName, - commandTypeName, - optionsExpression, - } = findConfig(ast); - const propProperties = getPropProperties(propsTypeName, types); - const commandProperties = getCommandProperties(commandTypeName, types); + const configType = getConfigType(ast); - const extendsProps = getExtendsProps(propProperties); - const options = getOptions(optionsExpression); + if (configType === 'component') { + const { + componentName, + propsTypeName, + commandTypeName, + optionsExpression, + } = findComponentConfig(ast); - const props = getProps(propProperties); - const events = getEvents(propProperties, types); - const commands = getCommands(commandProperties); + const propProperties = getPropProperties(propsTypeName, types); + const commandProperties = getCommandProperties(commandTypeName, types); - return { - filename: componentName, - componentName, - options, - extendsProps, - events, - props, - commands, - }; + const extendsProps = getExtendsProps(propProperties); + const options = getOptions(optionsExpression); + + const props = getProps(propProperties); + const events = getEvents(propProperties, types); + const commands = getCommands(commandProperties); + + return { + configType, + filename: componentName, + componentName, + options, + extendsProps, + events, + props, + commands, + }; + } else { + const {interfaceName, moduleName} = findModuleConfig(ast); + + const moduleProperties = getModuleProperties(types, interfaceName); + const properties = getMethods(moduleProperties); + return {configType, properties, filename: moduleName, moduleName}; + } } function parseFile(filename: string): ?SchemaType { diff --git a/packages/react-native-codegen/src/parsers/flow/methods.js b/packages/react-native-codegen/src/parsers/flow/methods.js new file mode 100644 index 00000000000..3744bc314c6 --- /dev/null +++ b/packages/react-native-codegen/src/parsers/flow/methods.js @@ -0,0 +1,33 @@ +/** + * 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 {MethodTypeShape} from '../../CodegenSchema.js'; + +function buildMethodSchema(property) { + return undefined; +} + +// $FlowFixMe there's no flowtype for ASTs +type PropAST = Object; + +function getMethods( + typeDefinition: $ReadOnlyArray, +): $ReadOnlyArray { + return typeDefinition + .filter(property => property.type === 'ObjectTypeProperty') + .map(buildMethodSchema) + .filter(Boolean); +} + +module.exports = { + getMethods, +}; diff --git a/packages/react-native-codegen/src/parsers/flow/schema.js b/packages/react-native-codegen/src/parsers/flow/schema.js index 9f784a915d7..3584912f7cd 100644 --- a/packages/react-native-codegen/src/parsers/flow/schema.js +++ b/packages/react-native-codegen/src/parsers/flow/schema.js @@ -19,7 +19,11 @@ import type { OptionsShape, } from '../../CodegenSchema.js'; -type SchemaBuilderConfig = $ReadOnly<{| +export type SchemaBuilderConfig = + | $ReadOnly<{|configType: 'module', ...NativeModuleSchemaBuilderConfig|}> + | $ReadOnly<{|configType: 'component', ...ComponentSchemaBuilderConfig|}>; + +type ComponentSchemaBuilderConfig = $ReadOnly<{| filename: string, componentName: string, extendsProps: $ReadOnlyArray, @@ -29,7 +33,26 @@ type SchemaBuilderConfig = $ReadOnly<{| options?: ?OptionsShape, |}>; -function buildSchema({ +type NativeModuleSchemaBuilderConfig = $ReadOnly<{| + filename: string, + moduleName: string, + properties: $ReadOnlyArray<{||}>, +|}>; + +function buildSchema(schemaInput: SchemaBuilderConfig): SchemaType { + if (schemaInput.configType === 'component') { + const {configType, ...componentSchemaInput} = schemaInput; + return buildComponentSchema(componentSchemaInput); + } else if (schemaInput.configType === 'module') { + const {configType, ...moduleSchemaInput} = schemaInput; + return buildNativeModuleSchema(moduleSchemaInput); + } + throw new Error( + 'Expected schema type of module or component, received wrong schema type', + ); +} + +function buildComponentSchema({ filename, componentName, extendsProps, @@ -37,7 +60,7 @@ function buildSchema({ props, options, commands, -}: SchemaBuilderConfig): SchemaType { +}: ComponentSchemaBuilderConfig): SchemaType { return { modules: { [filename]: { @@ -55,6 +78,24 @@ function buildSchema({ }; } +function buildNativeModuleSchema({ + filename, + moduleName, + properties, +}: NativeModuleSchemaBuilderConfig): SchemaType { + return { + modules: { + [filename]: { + nativeModules: { + [moduleName]: { + properties, + }, + }, + }, + }, + }; +} + module.exports = { buildSchema, };