From ce296c0e52a7b0870afc87f730e912bbdf65a46d Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Sat, 14 Mar 2026 21:39:25 -0700 Subject: [PATCH] Add centralized ReservedPrimitiveTypes registry and shared toSafeIdentifier helper (#56049) Summary: Reduce duplication and inconsistency across codegen generators by centralizing reserved primitive type mappings into a single `ReservedPrimitiveTypes.js` registry, making future type support and fixes a single-source change and lowering bug risk. Additionally, standardize identifier capitalization via a shared `toSafeIdentifier` helper in `Utils.js` to prevent divergent string handling across C++/Java helpers. Also removes dead TODO comments and obsolete commented-out code from `RNCodegen.js`, `GenerateModuleH.js`, and parser files. Changelog: [Internal] Differential Revision: D95711348 --- .../src/generators/RNCodegen.js | 6 - .../src/generators/ReservedPrimitiveTypes.js | 155 ++++++++++++++++++ .../src/generators/Utils.js | 13 +- .../components/ComponentsGeneratorUtils.js | 93 +++-------- .../src/generators/components/CppHelpers.js | 23 +-- .../src/generators/components/JavaHelpers.js | 44 +---- .../src/generators/modules/GenerateModuleH.js | 1 - .../src/parsers/flow/parser.js | 10 +- .../src/parsers/typescript/parser.js | 4 +- 9 files changed, 201 insertions(+), 148 deletions(-) create mode 100644 packages/react-native-codegen/src/generators/ReservedPrimitiveTypes.js diff --git a/packages/react-native-codegen/src/generators/RNCodegen.js b/packages/react-native-codegen/src/generators/RNCodegen.js index 5e439abd8f48..face426d207a 100644 --- a/packages/react-native-codegen/src/generators/RNCodegen.js +++ b/packages/react-native-codegen/src/generators/RNCodegen.js @@ -10,12 +10,6 @@ 'use strict'; -/* -TODO: - -- ViewConfigs should spread in View's valid attributes -*/ - import type {SchemaType} from '../CodegenSchema'; const schemaValidator = require('../SchemaValidator.js'); diff --git a/packages/react-native-codegen/src/generators/ReservedPrimitiveTypes.js b/packages/react-native-codegen/src/generators/ReservedPrimitiveTypes.js new file mode 100644 index 000000000000..97e521d78f52 --- /dev/null +++ b/packages/react-native-codegen/src/generators/ReservedPrimitiveTypes.js @@ -0,0 +1,155 @@ +/** + * 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. + * + * @flow strict + * @format + */ + +'use strict'; + +/** + * Single source of truth for the 6 reserved primitive types used in + * React Native component props. Each type maps to its per-language + * representation (C++ type name, C++ includes, Java imports). + * + * Previously these mappings were scattered across CppHelpers.js, + * ComponentsGeneratorUtils.js, and JavaHelpers.js. + */ + +export type ReservedPrimitiveName = + | 'ColorPrimitive' + | 'EdgeInsetsPrimitive' + | 'ImageRequestPrimitive' + | 'ImageSourcePrimitive' + | 'PointPrimitive' + | 'DimensionPrimitive'; + +type CppTypeInfo = { + +typeName: string, + +localIncludes: ReadonlyArray, + +conversionIncludes: ReadonlyArray, +}; + +type JavaImportInfo = { + +interfaceImports: ReadonlyArray, + +delegateImports: ReadonlyArray, +}; + +type ReservedTypeMapping = { + +cpp: CppTypeInfo, + +java: JavaImportInfo, +}; + +const RESERVED_TYPES: {+[ReservedPrimitiveName]: ReservedTypeMapping} = { + ColorPrimitive: { + cpp: { + typeName: 'SharedColor', + localIncludes: ['#include '], + conversionIncludes: [], + }, + java: { + interfaceImports: [], + delegateImports: ['import com.facebook.react.bridge.ColorPropConverter;'], + }, + }, + ImageSourcePrimitive: { + cpp: { + typeName: 'ImageSource', + localIncludes: ['#include '], + conversionIncludes: [ + '#include ', + ], + }, + java: { + interfaceImports: ['import com.facebook.react.bridge.ReadableMap;'], + delegateImports: ['import com.facebook.react.bridge.ReadableMap;'], + }, + }, + ImageRequestPrimitive: { + cpp: { + typeName: 'ImageRequest', + localIncludes: ['#include '], + conversionIncludes: [], + }, + java: { + // ImageRequestPrimitive is not used in Java component props + interfaceImports: [], + delegateImports: [], + }, + }, + PointPrimitive: { + cpp: { + typeName: 'Point', + localIncludes: ['#include '], + conversionIncludes: [], + }, + java: { + interfaceImports: ['import com.facebook.react.bridge.ReadableMap;'], + delegateImports: ['import com.facebook.react.bridge.ReadableMap;'], + }, + }, + EdgeInsetsPrimitive: { + cpp: { + typeName: 'EdgeInsets', + localIncludes: ['#include '], + conversionIncludes: [], + }, + java: { + interfaceImports: ['import com.facebook.react.bridge.ReadableMap;'], + delegateImports: ['import com.facebook.react.bridge.ReadableMap;'], + }, + }, + DimensionPrimitive: { + cpp: { + typeName: 'YGValue', + localIncludes: [ + '#include ', + '#include ', + ], + conversionIncludes: [ + '#include ', + ], + }, + java: { + interfaceImports: ['import com.facebook.yoga.YogaValue;'], + delegateImports: [ + 'import com.facebook.react.bridge.DimensionPropConverter;', + ], + }, + }, +}; + +function getCppTypeForReservedPrimitive(name: ReservedPrimitiveName): string { + return RESERVED_TYPES[name].cpp.typeName; +} + +function getCppLocalIncludesForReservedPrimitive( + name: ReservedPrimitiveName, +): ReadonlyArray { + return RESERVED_TYPES[name].cpp.localIncludes; +} + +function getCppConversionIncludesForReservedPrimitive( + name: ReservedPrimitiveName, +): ReadonlyArray { + return RESERVED_TYPES[name].cpp.conversionIncludes; +} + +function getJavaImportsForReservedPrimitive( + name: ReservedPrimitiveName, + type: 'interface' | 'delegate', +): ReadonlyArray { + const info = RESERVED_TYPES[name].java; + return type === 'interface' ? info.interfaceImports : info.delegateImports; +} + +module.exports = { + RESERVED_TYPES, + getCppTypeForReservedPrimitive, + getCppLocalIncludesForReservedPrimitive, + getCppConversionIncludesForReservedPrimitive, + getJavaImportsForReservedPrimitive, +}; diff --git a/packages/react-native-codegen/src/generators/Utils.js b/packages/react-native-codegen/src/generators/Utils.js index 7fcd65eeeb13..b503ae805ff1 100644 --- a/packages/react-native-codegen/src/generators/Utils.js +++ b/packages/react-native-codegen/src/generators/Utils.js @@ -34,11 +34,19 @@ function toPascalCase(inString: string): string { return inString; } - return inString[0].toUpperCase() + inString.slice(1); + return capitalize(inString); +} + +function toSafeIdentifier(input: string, shouldCapitalize: boolean): string { + const parts = input.split('-'); + if (!shouldCapitalize) { + return parts.join(''); + } + return parts.map(toPascalCase).join(''); } function toSafeCppString(input: string): string { - return input.split('-').map(toPascalCase).join(''); + return toSafeIdentifier(input, true); } function getEnumName(moduleName: string, origEnumName: string): string { @@ -105,6 +113,7 @@ module.exports = { indent, parseValidUnionType, toPascalCase, + toSafeIdentifier, toSafeCppString, getEnumName, HeterogeneousUnionError, diff --git a/packages/react-native-codegen/src/generators/components/ComponentsGeneratorUtils.js b/packages/react-native-codegen/src/generators/components/ComponentsGeneratorUtils.js index 20d01db68618..2e5422b84e75 100644 --- a/packages/react-native-codegen/src/generators/components/ComponentsGeneratorUtils.js +++ b/packages/react-native-codegen/src/generators/components/ComponentsGeneratorUtils.js @@ -21,6 +21,10 @@ import type { StringTypeAnnotation, } from '../../CodegenSchema'; +const { + getCppLocalIncludesForReservedPrimitive, + getCppTypeForReservedPrimitive, +} = require('../ReservedPrimitiveTypes'); const {getEnumName} = require('../Utils'); const { generateStructName, @@ -66,23 +70,7 @@ function getNativeTypeFromAnnotation( case 'FloatTypeAnnotation': return getCppTypeForAnnotation(typeAnnotation.type); case 'ReservedPropTypeAnnotation': - switch (typeAnnotation.name) { - case 'ColorPrimitive': - return 'SharedColor'; - case 'ImageSourcePrimitive': - return 'ImageSource'; - case 'ImageRequestPrimitive': - return 'ImageRequest'; - case 'PointPrimitive': - return 'Point'; - case 'EdgeInsetsPrimitive': - return 'EdgeInsets'; - case 'DimensionPrimitive': - return 'YGValue'; - default: - (typeAnnotation.name: empty); - throw new Error('Received unknown ReservedPropTypeAnnotation'); - } + return getCppTypeForReservedPrimitive(typeAnnotation.name); case 'ArrayTypeAnnotation': { const arrayType = typeAnnotation.elementType.type; if (arrayType === 'ArrayTypeAnnotation') { @@ -175,43 +163,25 @@ function convertVariableToPointer( return value; } -const convertCtorParamToAddressType = (type: string): string => { - const typesToConvert: Set = new Set(); - typesToConvert.add('ImageSource'); +// Configuration for C++ type conversions of reserved types. +// Centralizes the knowledge of which types need special pointer/address handling. +const CTOR_PARAM_ADDRESS_TYPES: Set = new Set(['ImageSource']); +const SHARED_POINTER_TYPES: Set = new Set(['ImageRequest']); - return convertTypesToConstAddressIfNeeded(type, typesToConvert); -}; +const convertCtorParamToAddressType = (type: string): string => + convertTypesToConstAddressIfNeeded(type, CTOR_PARAM_ADDRESS_TYPES); -const convertCtorInitToSharedPointers = ( - type: string, - value: string, -): string => { - const typesToConvert: Set = new Set(); - typesToConvert.add('ImageRequest'); +const convertCtorInitToSharedPointers = (type: string, value: string): string => + convertValueToSharedPointerWithMove(type, value, SHARED_POINTER_TYPES); - return convertValueToSharedPointerWithMove(type, value, typesToConvert); -}; +const convertGettersReturnTypeToAddressType = (type: string): string => + convertTypesToConstAddressIfNeeded(type, SHARED_POINTER_TYPES); -const convertGettersReturnTypeToAddressType = (type: string): string => { - const typesToConvert: Set = new Set(); - typesToConvert.add('ImageRequest'); +const convertVarTypeToSharedPointer = (type: string): string => + convertVariableToSharedPointer(type, SHARED_POINTER_TYPES); - return convertTypesToConstAddressIfNeeded(type, typesToConvert); -}; - -const convertVarTypeToSharedPointer = (type: string): string => { - const typesToConvert: Set = new Set(); - typesToConvert.add('ImageRequest'); - - return convertVariableToSharedPointer(type, typesToConvert); -}; - -const convertVarValueToPointer = (type: string, value: string): string => { - const typesToConvert: Set = new Set(); - typesToConvert.add('ImageRequest'); - - return convertVariableToPointer(type, value, typesToConvert); -}; +const convertVarValueToPointer = (type: string, value: string): string => + convertVariableToPointer(type, value, SHARED_POINTER_TYPES); function getLocalImports( properties: ReadonlyArray>, @@ -227,29 +197,8 @@ function getLocalImports( | 'ImageRequestPrimitive' | 'DimensionPrimitive', ) { - switch (name) { - case 'ColorPrimitive': - imports.add('#include '); - return; - case 'ImageSourcePrimitive': - imports.add('#include '); - return; - case 'ImageRequestPrimitive': - imports.add('#include '); - return; - case 'PointPrimitive': - imports.add('#include '); - return; - case 'EdgeInsetsPrimitive': - imports.add('#include '); - return; - case 'DimensionPrimitive': - imports.add('#include '); - imports.add('#include '); - return; - default: - (name: empty); - throw new Error(`Invalid ReservedPropTypeAnnotation name, got ${name}`); + for (const include of getCppLocalIncludesForReservedPrimitive(name)) { + imports.add(include); } } diff --git a/packages/react-native-codegen/src/generators/components/CppHelpers.js b/packages/react-native-codegen/src/generators/components/CppHelpers.js index e09977af1058..e344284252c6 100644 --- a/packages/react-native-codegen/src/generators/components/CppHelpers.js +++ b/packages/react-native-codegen/src/generators/components/CppHelpers.js @@ -15,6 +15,9 @@ import type { PropTypeAnnotation, } from '../../CodegenSchema'; +const { + getCppConversionIncludesForReservedPrimitive, +} = require('../ReservedPrimitiveTypes'); const {getEnumName, parseValidUnionType, toSafeCppString} = require('../Utils'); function toIntEnumValueName(propName: string, value: number): string { @@ -134,24 +137,8 @@ function getImports( | 'PointPrimitive' | 'DimensionPrimitive', ) { - switch (name) { - case 'ColorPrimitive': - return; - case 'PointPrimitive': - return; - case 'EdgeInsetsPrimitive': - return; - case 'ImageRequestPrimitive': - return; - case 'ImageSourcePrimitive': - imports.add('#include '); - return; - case 'DimensionPrimitive': - imports.add('#include '); - return; - default: - (name: empty); - throw new Error(`Invalid name, got ${name}`); + for (const include of getCppConversionIncludesForReservedPrimitive(name)) { + imports.add(include); } } diff --git a/packages/react-native-codegen/src/generators/components/JavaHelpers.js b/packages/react-native-codegen/src/generators/components/JavaHelpers.js index 40f7bf9a904d..ea6aae15eb21 100644 --- a/packages/react-native-codegen/src/generators/components/JavaHelpers.js +++ b/packages/react-native-codegen/src/generators/components/JavaHelpers.js @@ -12,9 +12,10 @@ import type {ComponentShape} from '../../CodegenSchema'; -function upperCaseFirst(inString: string): string { - return inString[0].toUpperCase() + inString.slice(1); -} +const { + getJavaImportsForReservedPrimitive, +} = require('../ReservedPrimitiveTypes'); +const {toSafeIdentifier} = require('../Utils'); function getInterfaceJavaClassName(componentName: string): string { return `${componentName.replace(/^RCT/, '')}ManagerInterface`; @@ -28,13 +29,7 @@ function toSafeJavaString( input: string, shouldUpperCaseFirst?: boolean, ): string { - const parts = input.split('-'); - - if (shouldUpperCaseFirst === false) { - return parts.join(''); - } - - return parts.map(upperCaseFirst).join(''); + return toSafeIdentifier(input, shouldUpperCaseFirst !== false); } function getImports( @@ -74,33 +69,8 @@ function getImports( | 'PointPrimitive' | 'DimensionPrimitive', ) { - switch (name) { - case 'ColorPrimitive': - if (type === 'delegate') { - imports.add('import com.facebook.react.bridge.ColorPropConverter;'); - } - return; - case 'ImageSourcePrimitive': - imports.add('import com.facebook.react.bridge.ReadableMap;'); - return; - case 'PointPrimitive': - imports.add('import com.facebook.react.bridge.ReadableMap;'); - return; - case 'EdgeInsetsPrimitive': - imports.add('import com.facebook.react.bridge.ReadableMap;'); - return; - case 'DimensionPrimitive': - if (type === 'delegate') { - imports.add( - 'import com.facebook.react.bridge.DimensionPropConverter;', - ); - } else { - imports.add('import com.facebook.yoga.YogaValue;'); - } - return; - default: - (name: empty); - throw new Error(`Invalid ReservedPropTypeAnnotation name, got ${name}`); + for (const javaImport of getJavaImportsForReservedPrimitive(name, type)) { + imports.add(javaImport); } } diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js index dddf253913ac..a2e16ff06e73 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js @@ -68,7 +68,6 @@ function serializeArg( // param?: T if (optional && !nullable) { - // throw new Error('are we hitting this case? ' + moduleName); return `count <= ${index} || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`; } diff --git a/packages/react-native-codegen/src/parsers/flow/parser.js b/packages/react-native-codegen/src/parsers/flow/parser.js index ac2c351c9ad3..bf3c77b2ffe7 100644 --- a/packages/react-native-codegen/src/parsers/flow/parser.js +++ b/packages/react-native-codegen/src/parsers/flow/parser.js @@ -272,15 +272,7 @@ class FlowParser implements Parser { return types[typeAnnotation.typeParameters.params[0].id.name]; } - /** - * This FlowFixMe is supposed to refer to an InterfaceDeclaration or TypeAlias - * declaration type. Unfortunately, we don't have those types, because flow-parser - * generates them, and flow-parser is not type-safe. In the future, we should find - * a way to get these types from our flow parser library. - * - * TODO(T71778680): Flow type AST Nodes - */ - + // TODO(T71778680): Flow type AST Nodes getTypes(ast: $FlowFixMe): TypeDeclarationMap { return ast.body.reduce((types, node) => { if ( diff --git a/packages/react-native-codegen/src/parsers/typescript/parser.js b/packages/react-native-codegen/src/parsers/typescript/parser.js index b3f4d603b4d3..86c9bdfade58 100644 --- a/packages/react-native-codegen/src/parsers/typescript/parser.js +++ b/packages/react-native-codegen/src/parsers/typescript/parser.js @@ -298,9 +298,7 @@ class TypeScriptParser implements Parser { return types[typeAnnotation.typeParameters.params[0].typeName.name]; } - /** - * TODO(T108222691): Use flow-types for @babel/parser - */ + // TODO(T108222691): Use flow-types for @babel/parser getTypes(ast: $FlowFixMe): TypeDeclarationMap { return ast.body.reduce((types, node) => { switch (node.type) {