diff --git a/.eslintrc.js b/.eslintrc.js index 5c5f2e298ae5..5f5b69ad614b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -61,6 +61,13 @@ module.exports = { files: ['package.json'], parser: 'jsonc-eslint-parser', }, + { + // Generated bundles carry `@nolint`, which this lint run does not honor. + files: ['private/eslint-plugin-monorepo/rules/sort-imports.js'], + rules: { + 'ft-flow/require-valid-file-annotation': 'off', + }, + }, { files: ['flow-typed/**/*.js', 'packages/react-native/flow/**/*'], rules: { diff --git a/package.json b/package.json index 1a3d1a77c884..b7b90d827b12 100644 --- a/package.json +++ b/package.json @@ -97,11 +97,11 @@ "eslint-plugin-redundant-undefined": "^0.4.0", "eslint-plugin-relay": "^1.8.3", "fb-dotslash": "0.5.8", - "flow-api-translator": "0.336.0", + "flow-api-translator": "0.340.0", "flow-bin": "^0.340.0", - "flow-eslint": "0.336.0", - "flow-parser": "0.336.0", - "flow-transform": "0.336.0", + "flow-eslint": "0.340.0", + "flow-parser": "0.340.0", + "flow-transform": "0.340.0", "google-java-format": "1.4.0", "ini": "^5.0.0", "inquirer": "^7.1.0", diff --git a/packages/eslint-plugin-react-native/package.json b/packages/eslint-plugin-react-native/package.json index a0b85a8b98cc..e2445fdef0fc 100644 --- a/packages/eslint-plugin-react-native/package.json +++ b/packages/eslint-plugin-react-native/package.json @@ -25,8 +25,8 @@ "utils.js" ], "devDependencies": { - "flow-parser": "0.336.0", - "flow-eslint": "0.336.0" + "flow-parser": "0.340.0", + "flow-eslint": "0.340.0" }, "engines": { "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" diff --git a/packages/eslint-plugin-specs/package.json b/packages/eslint-plugin-specs/package.json index 6bcbc76ae503..a29cf114d49f 100644 --- a/packages/eslint-plugin-specs/package.json +++ b/packages/eslint-plugin-specs/package.json @@ -35,11 +35,11 @@ "@react-native/codegen": "0.87.0-main", "make-dir": "^2.1.0", "pirates": "^4.0.1", - "flow-parser": "0.336.0", + "flow-parser": "0.340.0", "source-map-support": "0.5.0" }, "devDependencies": { - "flow-eslint": "0.336.0" + "flow-eslint": "0.340.0" }, "engines": { "node": "^22.13.0 || ^24.3.0 || >= 26.0.0" diff --git a/packages/react-native-babel-preset/package.json b/packages/react-native-babel-preset/package.json index 759195dbf1c3..5858cb01999e 100644 --- a/packages/react-native-babel-preset/package.json +++ b/packages/react-native-babel-preset/package.json @@ -50,7 +50,7 @@ "@babel/plugin-transform-unicode-property-regex": "^7.25.9", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@react-native/babel-plugin-codegen": "0.87.0-main", - "flow-parser": "0.336.0", + "flow-parser": "0.340.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, diff --git a/packages/react-native-babel-transformer/package.json b/packages/react-native-babel-transformer/package.json index 44cc91c84153..883a8a3320de 100644 --- a/packages/react-native-babel-transformer/package.json +++ b/packages/react-native-babel-transformer/package.json @@ -28,7 +28,7 @@ "dependencies": { "@babel/core": "^7.25.2", "@react-native/babel-preset": "0.87.0-main", - "flow-parser": "0.336.0", + "flow-parser": "0.340.0", "nullthrows": "^1.1.1" }, "peerDependencies": { diff --git a/packages/react-native-codegen/package.json b/packages/react-native-codegen/package.json index 7162ae5013c9..0b8496f1cb25 100644 --- a/packages/react-native-codegen/package.json +++ b/packages/react-native-codegen/package.json @@ -31,7 +31,7 @@ "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.9", - "flow-parser": "0.336.0", + "flow-parser": "0.340.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", @@ -45,7 +45,7 @@ "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/preset-env": "^7.25.3", - "flow-estree": "0.336.0", + "flow-estree": "0.340.0", "micromatch": "^4.0.4", "prettier": "3.9.4", "rimraf": "^3.0.2" diff --git a/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js b/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js index e7de0a890da6..5f6460ad4b76 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js @@ -459,6 +459,67 @@ describe('FlowParser', () => { expect(parser.getLiteralValue(option)).toEqual(undefined); }); }); + + describe('parseEnumMembersType', () => { + const parseEnumBody = (code: string) => + parser.getAst(code).body[0].declaration.body; + + it.each([ + ['export enum E {A, B}', 'StringTypeAnnotation'], + [`export enum E {A = 'a'}`, 'StringTypeAnnotation'], + ['export enum E of string {A}', 'StringTypeAnnotation'], + ['export enum E {}', 'StringTypeAnnotation'], + ['export enum E {A = 1}', 'NumberTypeAnnotation'], + ['export enum E of number {A = 1}', 'NumberTypeAnnotation'], + ['export enum E of number {}', 'NumberTypeAnnotation'], + ])('returns the member type of a parsed `%s`', (code, expected) => { + expect(parser.parseEnumMembersType(parseEnumBody(code))).toBe(expected); + }); + + it.each([ + 'export enum E {A = true}', + 'export enum E of boolean {A = true}', + 'export enum E of symbol {A}', + ])('throws for a parsed `%s`', code => { + expect(() => parser.parseEnumMembersType(parseEnumBody(code))).toThrow( + 'Unknown enum type annotation type.', + ); + }); + + it('supports the body node types of older parser versions', () => { + expect(parser.parseEnumMembersType({type: 'EnumStringBody'})).toBe( + 'StringTypeAnnotation', + ); + expect(parser.parseEnumMembersType({type: 'EnumNumberBody'})).toBe( + 'NumberTypeAnnotation', + ); + expect(() => + parser.parseEnumMembersType({type: 'EnumBooleanBody'}), + ).toThrow('Unknown enum type annotation type.'); + }); + + it('rejects a parsed boolean enum in a module spec', () => { + expect(() => + parser.parseString( + ` + import type {TurboModule} from 'RCTExport'; + import * as TurboModuleRegistry from 'TurboModuleRegistry'; + + export enum SomeEnum { + A = true, + } + + export interface Spec extends TurboModule { + +getEnum: (a: SomeEnum) => string; + } + + export default TurboModuleRegistry.get('NativeSampleTurboModule'); + `, + 'path/NativeSampleTurboModule.js', + ), + ).toThrow(/Flow type annotation 'Enum\w*Body' is unsupported/); + }); + }); }); describe('TypeScriptParser', () => { diff --git a/packages/react-native-codegen/src/parsers/__tests__/utils-test.js b/packages/react-native-codegen/src/parsers/__tests__/utils-test.js index 176347503dae..30364d051f24 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/utils-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/utils-test.js @@ -11,9 +11,12 @@ 'use strict'; const {ParserError} = require('../errors'); +const {FlowParser} = require('../flow/parser'); +const {TypeScriptParser} = require('../typescript/parser'); const { createParserErrorCapturer, extractNativeModuleName, + getTypeArguments, verifyPlatforms, visit, } = require('../utils.js'); @@ -292,3 +295,49 @@ describe('visit', () => { }); }); }); + +describe('getTypeArguments', () => { + const getAliasedType = ( + parser: FlowParser | TypeScriptParser, + code: string, + ) => { + const declaration = parser.getAst(code).body[0]; + return declaration.right ?? declaration.typeAnnotation; + }; + + it('returns the type arguments of a parsed Flow generic type', () => { + const typeArguments = getTypeArguments( + getAliasedType(new FlowParser(), 'type T = Readonly<{a: string}>;'), + ); + + expect(typeArguments.type).toBe('TypeParameterInstantiation'); + expect(typeArguments.params.map(param => param.type)).toEqual([ + 'ObjectTypeAnnotation', + ]); + }); + + it('returns no type arguments for a parsed non-generic Flow type', () => { + expect( + getTypeArguments(getAliasedType(new FlowParser(), 'type T = Foo;')) ?? + null, + ).toBeNull(); + }); + + it('returns the type arguments of a parsed TypeScript generic type', () => { + const typeArguments = getTypeArguments( + getAliasedType(new TypeScriptParser(), 'type T = Readonly<{a: string}>;'), + ); + + expect(typeArguments.type).toBe('TSTypeParameterInstantiation'); + expect(typeArguments.params.map(param => param.type)).toEqual([ + 'TSTypeLiteral', + ]); + }); + + it('prefers typeArguments, including null, over typeParameters', () => { + const typeParameters = {type: 'TypeParameterInstantiation', params: []}; + + expect(getTypeArguments({typeArguments: null, typeParameters})).toBeNull(); + expect(getTypeArguments({typeParameters})).toBe(typeParameters); + }); +}); diff --git a/packages/react-native-codegen/src/parsers/error-utils.js b/packages/react-native-codegen/src/parsers/error-utils.js index c082883bd5a3..be7d652d3ae3 100644 --- a/packages/react-native-codegen/src/parsers/error-utils.js +++ b/packages/react-native-codegen/src/parsers/error-utils.js @@ -35,6 +35,7 @@ const { UntypedModuleRegistryCallParserError, UnusedModuleInterfaceParserError, } = require('./errors'); +const {getTypeArguments} = require('./utils'); function throwIfModuleInterfaceIsMisnamed( nativeModuleName: string, @@ -360,7 +361,7 @@ function throwIfPartialNotAnnotatingTypeParameter( } function throwIfPartialWithMoreParameter(typeAnnotation: $FlowFixMe) { - if (typeAnnotation.typeParameters.params.length !== 1) { + if (getTypeArguments(typeAnnotation).params.length !== 1) { throw new Error('Partials only support annotating exactly one parameter.'); } } diff --git a/packages/react-native-codegen/src/parsers/flow/components/commands.js b/packages/react-native-codegen/src/parsers/flow/components/commands.js index de495e00d1bf..03c7eaa03bbc 100644 --- a/packages/react-native-codegen/src/parsers/flow/components/commands.js +++ b/packages/react-native-codegen/src/parsers/flow/components/commands.js @@ -19,6 +19,7 @@ import type { import type {Parser} from '../../parser'; import type {TypeDeclarationMap} from '../../utils'; +const {getTypeArguments} = require('../../utils'); const {getValueFromTypes} = require('../utils.js'); // $FlowFixMe[unclear-type] there's no flowtype for ASTs @@ -114,7 +115,7 @@ function buildCommandSchema( returnType = { type: 'ArrayTypeAnnotation', elementType: getCommandArrayElementTypeType( - paramValue.typeParameters.params[0], + getTypeArguments(paramValue).params[0], parser, ), }; diff --git a/packages/react-native-codegen/src/parsers/flow/components/componentsUtils.js b/packages/react-native-codegen/src/parsers/flow/components/componentsUtils.js index b15150f9f573..b2c59eab6264 100644 --- a/packages/react-native-codegen/src/parsers/flow/components/componentsUtils.js +++ b/packages/react-native-codegen/src/parsers/flow/components/componentsUtils.js @@ -14,6 +14,7 @@ import type {BuildSchemaFN, Parser} from '../../parser'; import type {ASTNode, PropAST, TypeDeclarationMap} from '../../utils'; const {verifyPropNotAlreadyDefined} = require('../../parsers-commons'); +const {getTypeArguments} = require('../../utils'); const {getValueFromTypes} = require('../utils.js'); // $FlowFixMe[unsupported-variance-annotation] @@ -52,7 +53,7 @@ function getTypeAnnotationForArray( return { type: 'ObjectTypeAnnotation', properties: flattenProperties( - objectType.typeParameters.params[0].properties, + getTypeArguments(objectType).params[0].properties, types, parser, ) @@ -68,7 +69,7 @@ function getTypeAnnotationForArray( // We need to go yet another level deeper to resolve // types that may be defined in a type alias const nestedObjectType = getValueFromTypes( - objectType.typeParameters.params[0], + getTypeArguments(objectType).params[0], types, ); @@ -77,7 +78,7 @@ function getTypeAnnotationForArray( elementType: { type: 'ObjectTypeAnnotation', properties: flattenProperties( - nestedObjectType.typeParameters.params[0].properties, + getTypeArguments(nestedObjectType).params[0].properties, types, parser, ) @@ -239,7 +240,7 @@ function getTypeAnnotation( type: 'ArrayTypeAnnotation', elementType: getTypeAnnotationForArray( name, - typeAnnotation.typeParameters.params[0], + getTypeArguments(typeAnnotation).params[0], defaultValue, types, parser, @@ -256,7 +257,7 @@ function getTypeAnnotation( return { type: 'ObjectTypeAnnotation', properties: flattenProperties( - typeAnnotation.typeParameters.params[0].properties, + getTypeArguments(typeAnnotation).params[0].properties, types, parser, ) @@ -467,16 +468,16 @@ function getSchemaInfo( type === 'GenericTypeAnnotation' && typeAnnotationName === 'WithDefault' ) { - if (typeAnnotation.typeParameters.params.length === 1) { + if (getTypeArguments(typeAnnotation).params.length === 1) { throw new Error( `WithDefault requires two parameters, did you forget to provide a default value for "${name}"?`, ); } - defaultValue = typeAnnotation.typeParameters.params[1].value; - const defaultValueType = typeAnnotation.typeParameters.params[1].type; + defaultValue = getTypeArguments(typeAnnotation).params[1].value; + const defaultValueType = getTypeArguments(typeAnnotation).params[1].type; - typeAnnotation = typeAnnotation.typeParameters.params[0]; + typeAnnotation = getTypeArguments(typeAnnotation).params[0]; type = typeAnnotation.type === 'GenericTypeAnnotation' ? typeAnnotationName diff --git a/packages/react-native-codegen/src/parsers/flow/components/events.js b/packages/react-native-codegen/src/parsers/flow/components/events.js index 484a049befbd..20e725c08ef8 100644 --- a/packages/react-native-codegen/src/parsers/flow/components/events.js +++ b/packages/react-native-codegen/src/parsers/flow/components/events.js @@ -39,6 +39,7 @@ const { emitStringProp, emitUnionProp, } = require('../../parsers-primitives'); +const {getTypeArguments} = require('../../utils'); function getPropertyType( /* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's @@ -66,7 +67,7 @@ function getPropertyType( return getPropertyType( name, optional, - typeAnnotation.typeParameters.params[0], + getTypeArguments(typeAnnotation).params[0], parser, ); case 'ObjectTypeAnnotation': @@ -145,7 +146,7 @@ function extractArrayElementType( }; case '$ReadOnlyArray': case 'ReadonlyArray': - const genericParams = typeAnnotation.typeParameters.params; + const genericParams = getTypeArguments(typeAnnotation).params; if (genericParams.length !== 1) { throw new Error( `Events only supports arrays with 1 Generic type. Found ${ @@ -190,7 +191,7 @@ function findEventArgumentsAndType( const name = parser.getTypeAnnotationName(typeAnnotation); if (name === '$ReadOnly' || name === 'Readonly') { return { - argumentProps: typeAnnotation.typeParameters.params[0].properties, + argumentProps: getTypeArguments(typeAnnotation).params[0].properties, paperTopLevelNameDeprecated: paperName, bubblingType, }; 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 215a5db8aaa5..1def029e4d99 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -47,6 +47,8 @@ const { typeAliasResolution, typeEnumResolution, } = require('../../parsers-primitives'); +const {getTypeArguments} = require('../../utils'); +const {getEnumMembersType} = require('../utils'); function translateTypeAnnotation( hasteModuleName: string, @@ -125,7 +127,7 @@ function translateTypeAnnotation( const [paramType, isParamNullable] = unwrapNullable( translateTypeAnnotation( hasteModuleName, - typeAnnotation.typeParameters.params[0], + getTypeArguments(typeAnnotation).params[0], types, aliasMap, enumMap, @@ -274,10 +276,19 @@ function translateTypeAnnotation( value: typeAnnotation.value, }); } + case 'EnumBody': case 'EnumStringBody': case 'EnumNumberBody': { + const enumMembersType = getEnumMembersType(typeAnnotation); + if (enumMembersType == null) { + throw new UnsupportedTypeAnnotationParserError( + hasteModuleName, + typeAnnotation, + parser.language(), + ); + } if ( - typeAnnotation.type === 'EnumNumberBody' && + enumMembersType === 'NumberTypeAnnotation' && typeAnnotation.members.some( m => m.type === 'EnumNumberMember' && !Number.isInteger(m.init?.value), diff --git a/packages/react-native-codegen/src/parsers/flow/parser.js b/packages/react-native-codegen/src/parsers/flow/parser.js index afc3f7bf07dd..7fb41f7117fe 100644 --- a/packages/react-native-codegen/src/parsers/flow/parser.js +++ b/packages/react-native-codegen/src/parsers/flow/parser.js @@ -48,6 +48,7 @@ const { } = require('../parsers-commons'); const {Visitor} = require('../parsers-primitives'); const {wrapComponentSchema} = require('../schema.js'); +const {getTypeArguments} = require('../utils'); const {buildComponentSchema} = require('./components'); const { flattenProperties, @@ -56,6 +57,7 @@ const { } = require('./components/componentsUtils'); const {flowTranslateTypeAnnotation} = require('./modules'); const {parseFlowAndThrowErrors} = require('./parseFlowAndThrowErrors'); +const {getEnumMembersType} = require('./utils'); const invariant = require('invariant'); const fs = require('node:fs'); @@ -177,15 +179,10 @@ class FlowParser implements Parser { } parseEnumMembersType(typeAnnotation: $FlowFixMe): NativeModuleEnumMemberType { - const enumMembersType: ?NativeModuleEnumMemberType = - typeAnnotation.type === 'EnumStringBody' - ? 'StringTypeAnnotation' - : typeAnnotation.type === 'EnumNumberBody' - ? 'NumberTypeAnnotation' - : null; + const enumMembersType = getEnumMembersType(typeAnnotation); if (!enumMembersType) { throw new Error( - `Unknown enum type annotation type. Got: ${typeAnnotation.type}. Expected: EnumStringBody or EnumNumberBody.`, + `Unknown enum type annotation type. Got: ${typeAnnotation.type}. Expected a string or number enum body.`, ); } return enumMembersType; @@ -269,7 +266,7 @@ class FlowParser implements Parser { typeAnnotation: $FlowFixMe, types: TypeDeclarationMap, ): $FlowFixMe { - return types[typeAnnotation.typeParameters.params[0].id.name]; + return types[getTypeArguments(typeAnnotation).params[0].id.name]; } // TODO(T71778680): Flow type AST Nodes @@ -533,7 +530,7 @@ class FlowParser implements Parser { getProperties(typeName: string, types: TypeDeclarationMap): $FlowFixMe { const typeAlias = types[typeName]; try { - return typeAlias.right.typeParameters.params[0].properties; + return getTypeArguments(typeAlias.right).params[0].properties; } catch (e) { throw new Error( `Failed to find type definition for "${typeName}", please check that you have a valid codegen flow file`, @@ -572,8 +569,8 @@ class FlowParser implements Parser { } getPaperTopLevelNameDeprecated(typeAnnotation: $FlowFixMe): $FlowFixMe { - return typeAnnotation.typeParameters.params.length > 1 - ? typeAnnotation.typeParameters.params[1].value + return getTypeArguments(typeAnnotation).params.length > 1 + ? getTypeArguments(typeAnnotation).params[1].value : null; } } diff --git a/packages/react-native-codegen/src/parsers/flow/utils.js b/packages/react-native-codegen/src/parsers/flow/utils.js index 02af59166068..b22ba9d72414 100644 --- a/packages/react-native-codegen/src/parsers/flow/utils.js +++ b/packages/react-native-codegen/src/parsers/flow/utils.js @@ -10,6 +10,7 @@ 'use strict'; +import type {NativeModuleEnumMemberType} from '../../CodegenSchema'; import type {ASTNode, TypeDeclarationMap} from '../utils'; function getValueFromTypes(value: ASTNode, types: TypeDeclarationMap): ASTNode { @@ -19,6 +20,47 @@ function getValueFromTypes(value: ASTNode, types: TypeDeclarationMap): ASTNode { return value; } +/** + * Returns the member type of an enum body, or `null` if it is not a string or + * number enum. Older Flow parser versions encode the member type in the body + * node type. Newer versions emit an `EnumBody` whose `explicitType` is `null` + * when the enum has no `of` clause, in which case the first member decides. + */ +function getEnumMembersType(enumBody: ASTNode): ?NativeModuleEnumMemberType { + switch (enumBody.type) { + case 'EnumStringBody': + return 'StringTypeAnnotation'; + case 'EnumNumberBody': + return 'NumberTypeAnnotation'; + case 'EnumBody': + break; + default: + return null; + } + + switch (enumBody.explicitType) { + case 'number': + return 'NumberTypeAnnotation'; + case 'string': + case null: + break; + default: + return null; + } + + switch (enumBody.members[0]?.type) { + case 'EnumNumberMember': + return 'NumberTypeAnnotation'; + case 'EnumStringMember': + case 'EnumDefaultedMember': + case undefined: + return 'StringTypeAnnotation'; + default: + return null; + } +} + module.exports = { + getEnumMembersType, getValueFromTypes, }; diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index 3e3c1fec6c2d..583c30ec8f38 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -72,6 +72,7 @@ const { extractNativeModuleName, getConfigType, getSortedObject, + getTypeArguments, isModuleRegistryCall, verifyPlatforms, visit, @@ -141,7 +142,7 @@ function assertGenericTypeAnnotationHasExactlyOneTypeParameter( typeAnnotation: $FlowFixMe, parser: Parser, ) { - if (typeAnnotation.typeParameters == null) { + if (getTypeArguments(typeAnnotation) == null) { throw new MissingTypeParameterGenericParserError( moduleName, typeAnnotation, @@ -152,11 +153,11 @@ function assertGenericTypeAnnotationHasExactlyOneTypeParameter( const typeAnnotationType = parser.typeParameterInstantiation; invariant( - typeAnnotation.typeParameters.type === typeAnnotationType, + getTypeArguments(typeAnnotation).type === typeAnnotationType, `assertGenericTypeAnnotationHasExactlyOneTypeParameter: Type parameters must be an AST node of type '${typeAnnotationType}'`, ); - if (typeAnnotation.typeParameters.params.length !== 1) { + if (getTypeArguments(typeAnnotation).params.length !== 1) { throw new MoreThanOneTypeParameterGenericParserError( moduleName, typeAnnotation, @@ -195,9 +196,9 @@ function getObjectTypeAnnotations( if ( parent.type === 'GenericTypeAnnotation' && (parent.id?.name === '$ReadOnly' || parent.id?.name === 'Readonly') && - parent.typeParameters?.params?.length === 1 + getTypeArguments(parent)?.params?.length === 1 ) { - parent = parent.typeParameters.params[0]; + parent = getTypeArguments(parent).params[0]; } else if ( parent.type === 'TSTypeReference' && parent.typeName?.name === 'Readonly' && @@ -513,12 +514,12 @@ function buildEventEmitterSchema( const resolveTypeAnnotationFN = parser.getResolveTypeAnnotationFN(); const [typeAnnotation, typeAnnotationNullable] = unwrapNullable(value); const typeAnnotationUntyped = - value.typeParameters.params.length === 1 && + getTypeArguments(value).params.length === 1 && parser.language() === 'TypeScript' - ? value.typeParameters.params[0].type === 'TSTypeLiteral' && - value.typeParameters.params[0].members.length === 0 - : value.typeParameters.params[0].type === 'ObjectTypeAnnotation' && - value.typeParameters.params[0].properties.length === 0; + ? getTypeArguments(value).params[0].type === 'TSTypeLiteral' && + getTypeArguments(value).params[0].members.length === 0 + : getTypeArguments(value).params[0].type === 'ObjectTypeAnnotation' && + getTypeArguments(value).params[0].properties.length === 0; throwIfEventEmitterTypeIsUnsupported( hasteModuleName, @@ -529,7 +530,7 @@ function buildEventEmitterSchema( typeAnnotationUntyped, ); const eventTypeResolutionStatus = resolveTypeAnnotationFN( - typeAnnotation.typeParameters.params[0], + getTypeArguments(typeAnnotation).params[0], types, parser, ); @@ -543,7 +544,7 @@ function buildEventEmitterSchema( const eventTypeAnnotation = translateTypeAnnotation( hasteModuleName, - typeAnnotation.typeParameters.params[0], + getTypeArguments(typeAnnotation).params[0], types, aliasMap, enumMap, @@ -554,7 +555,7 @@ function buildEventEmitterSchema( throwIfEventEmitterPayloadTypeIsUnsupported( hasteModuleName, - typeAnnotation.typeParameters.params[0], + getTypeArguments(typeAnnotation).params[0], key.name, eventTypeAnnotation, ); @@ -1341,7 +1342,7 @@ function handleEventHandler( const paperTopLevelNameDeprecated = parser.getPaperTopLevelNameDeprecated(typeAnnotation); - switch (typeAnnotation.typeParameters.params[0].type) { + switch (getTypeArguments(typeAnnotation).params[0].type) { case parser.nullLiteralTypeAnnotation: case parser.undefinedLiteralTypeAnnotation: return { @@ -1352,7 +1353,7 @@ function handleEventHandler( default: return findEventArgumentsAndType( parser, - typeAnnotation.typeParameters.params[0], + getTypeArguments(typeAnnotation).params[0], types, eventType, paperTopLevelNameDeprecated, diff --git a/packages/react-native-codegen/src/parsers/parsers-primitives.js b/packages/react-native-codegen/src/parsers/parsers-primitives.js index 7c6de3bbe7ba..91dfc1b1b8fb 100644 --- a/packages/react-native-codegen/src/parsers/parsers-primitives.js +++ b/packages/react-native-codegen/src/parsers/parsers-primitives.js @@ -61,7 +61,7 @@ const { wrapNullable, } = require('./parsers-commons'); const {nullGuard} = require('./parsers-utils'); -const {isModuleRegistryCall} = require('./utils'); +const {getTypeArguments, isModuleRegistryCall} = require('./utils'); function emitBoolean(nullable: boolean): Nullable { return wrapNullable(nullable, { @@ -348,7 +348,7 @@ function emitPromise( parser, ); - const elementType = typeAnnotation.typeParameters.params[0]; + const elementType = getTypeArguments(typeAnnotation).params[0]; if ( elementType.type === 'ExistsTypeAnnotation' || elementType.type === 'EmptyTypeAnnotation' @@ -364,7 +364,7 @@ function emitPromise( try { elementTypeResult = translateTypeAnnotation( hasteModuleName, - typeAnnotation.typeParameters.params[0], + getTypeArguments(typeAnnotation).params[0], types, aliasMap, enumMap, @@ -574,7 +574,7 @@ function emitArrayType( enumMap, cxxOnly, typeAnnotation.type, - typeAnnotation.typeParameters.params[0], + getTypeArguments(typeAnnotation).params[0], nullable, translateTypeAnnotation, parser, diff --git a/packages/react-native-codegen/src/parsers/utils.js b/packages/react-native-codegen/src/parsers/utils.js index 6bf128cf9524..a0fd1dff2c15 100644 --- a/packages/react-native-codegen/src/parsers/utils.js +++ b/packages/react-native-codegen/src/parsers/utils.js @@ -199,6 +199,18 @@ function isModuleRegistryCall(node: $FlowFixMe): boolean { return true; } +/** + * Returns the type arguments of a generic type reference such as + * `Array`. Flow's ESTree output stores them in `typeArguments`, which is + * `null` when there are none; Babel's TypeScript AST and older Flow parser + * versions use `typeParameters`. + */ +function getTypeArguments(typeAnnotation: ASTNode): ASTNode { + return Object.hasOwn(typeAnnotation, 'typeArguments') + ? typeAnnotation.typeArguments + : typeAnnotation.typeParameters; +} + function getSortedObject(unsortedObject: {[key: string]: T}): { [key: string]: T, } { @@ -218,4 +230,5 @@ module.exports = { visit, isModuleRegistryCall, getSortedObject, + getTypeArguments, }; diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 0a5e87ee89d1..d0957b6bd91b 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -158,7 +158,7 @@ "@react-native/virtualized-lists": "0.87.0-main", "anser": "^1.4.9", "ansi-regex": "^5.0.0", - "flow-parser": "0.336.0", + "flow-parser": "0.340.0", "base64-js": "^1.5.1", "flow-enums-runtime": "^0.0.6", "hermes-compiler": "0.0.0", diff --git a/yarn.lock b/yarn.lock index e10440d14d52..cf3ff97bf67e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4840,19 +4840,19 @@ flora-colossus@^3.0.2: dependencies: debug "^4.4.1" -flow-api-translator@0.336.0: - version "0.336.0" - resolved "https://registry.yarnpkg.com/flow-api-translator/-/flow-api-translator-0.336.0.tgz#588c4672c6a32f7fcb00064314b7ad1551e728a5" - integrity sha512-0odorqlErVwnXLuFpQdpRwlUNaqklR2H2tN8uRmlV/Usb82k0S++ReH3afKL7BhUevi4xMsBa02vd38Lf8RznQ== +flow-api-translator@0.340.0: + version "0.340.0" + resolved "https://registry.yarnpkg.com/flow-api-translator/-/flow-api-translator-0.340.0.tgz#c064bd1b86d0f68432d3aff620a3e4fc1e8543b5" + integrity sha512-scSfD46icbYxjzN4a0zER1Jc++po4TXeIKwCKQlKGjE7QfYNKpdf3KoN8hjnAH1HsVjM14gDYglYKqQepZ+AiQ== dependencies: "@babel/code-frame" "^7.16.0" "@typescript-eslint/parser" "8.38.0" "@typescript-eslint/visitor-keys" "8.38.0" flow-enums-runtime "^0.0.6" - flow-eslint "0.336.0" - flow-estree "0.336.0" - flow-parser "0.336.0" - flow-transform "0.336.0" + flow-eslint "0.340.0" + flow-estree "0.340.0" + flow-parser "0.340.0" + flow-transform "0.340.0" typescript "5.3.2" flow-bin@^0.340.0: @@ -4865,20 +4865,25 @@ flow-enums-runtime@^0.0.6: resolved "https://registry.yarnpkg.com/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz#5bb0cd1b0a3e471330f4d109039b7eba5cb3e787" integrity sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw== -flow-eslint@0.336.0: - version "0.336.0" - resolved "https://registry.yarnpkg.com/flow-eslint/-/flow-eslint-0.336.0.tgz#2a9e807c85f41f8ba2329e787fd84bc67c411f6d" - integrity sha512-HWsiMD2mC3HbVwPKX74BxyX+ZjgiJi3BYDOG/FofbpLce/Eg1wftSuGEyvWprYpgKOnTEe/7o32IdLdTv25LaQ== +flow-eslint@0.340.0: + version "0.340.0" + resolved "https://registry.yarnpkg.com/flow-eslint/-/flow-eslint-0.340.0.tgz#dbca65729dd9d671ae06ff935b6a0897f22f8d9f" + integrity sha512-Ms9ViJJtdv5dMH3M4i6jD3j+YTg0DyNN8rzLQrBKHLcsxH0zFbv4uajpJK9SgLmw2RKaBuuwRXKdDgfsfanWKQ== dependencies: esrecurse "^4.3.0" - flow-estree "0.336.0" - flow-parser "0.336.0" + flow-estree "0.340.0" + flow-parser "0.340.0" flow-estree@0.336.0: version "0.336.0" resolved "https://registry.yarnpkg.com/flow-estree/-/flow-estree-0.336.0.tgz#e9e90c68ac69b078b2d167588e47a11e5d240d86" integrity sha512-0VaGWHJourzh88otU5bVJ4Z/oIsD+R6yaeR0Q8t8dezsKQI/RLi5m3fNzkiLYLhJ+YwM3JRSKlOqGplOI5Yo3w== +flow-estree@0.340.0: + version "0.340.0" + resolved "https://registry.yarnpkg.com/flow-estree/-/flow-estree-0.340.0.tgz#e86e368f5f33f4f5cc8871a18d3f79122569ebc2" + integrity sha512-LpYdzPWHsfDAc5nNmVrUPLo8UedFkJqogIxNyVO19tG7qBhvNwjs+Oo4TfWB+2Qa4arFevu3vdbxR5tfWps53g== + flow-parser@0.336.0, flow-parser@^0.336.0: version "0.336.0" resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.336.0.tgz#5c34f8a97edfa241d3e71c7a1dcd99a56b1cc2bc" @@ -4886,6 +4891,13 @@ flow-parser@0.336.0, flow-parser@^0.336.0: dependencies: flow-estree "0.336.0" +flow-parser@0.340.0: + version "0.340.0" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.340.0.tgz#7e1790634772a462612cc3d3347e2979ca4261af" + integrity sha512-zB1DgZcBVes/hHj/upWKLhukHAIkj+6g2o0yVnxCxS1Wyi/YLX513w8XpFLJ7/LUrbSE7CIHJAEZ94smHujt9Q== + dependencies: + flow-estree "0.340.0" + flow-remove-types@^2.336.0: version "2.336.0" resolved "https://registry.yarnpkg.com/flow-remove-types/-/flow-remove-types-2.336.0.tgz#0131b825a84dc216553e09e4e6597975bd25c77b" @@ -4895,17 +4907,17 @@ flow-remove-types@^2.336.0: pirates "^3.0.2" vlq "^0.2.1" -flow-transform@0.336.0: - version "0.336.0" - resolved "https://registry.yarnpkg.com/flow-transform/-/flow-transform-0.336.0.tgz#61fd6c761b63c412b67c707dcd81333d24e5fa8d" - integrity sha512-KQEt9y581jhxaUOM80sPbssa/t6bEnwGz0x/xfwmxXv8a69bs6wW83/1Roq1h9qoJfwvSTncWk3IlrpdBVBXXQ== +flow-transform@0.340.0: + version "0.340.0" + resolved "https://registry.yarnpkg.com/flow-transform/-/flow-transform-0.340.0.tgz#ae9fa16ba916378d25806606fa7073c921652e29" + integrity sha512-JC7YcbERLI0XV0D3BkAXCFkZktq0EILxXRvJsWoHjdAZAZe2hIS1DGOmhbRyFti6crskGD/ui8w8nLvM/SoPxg== dependencies: "@babel/code-frame" "^7.16.0" esquery "^1.4.0" flow-enums-runtime "^0.0.6" - flow-eslint "0.336.0" - flow-estree "0.336.0" - flow-parser "0.336.0" + flow-eslint "0.340.0" + flow-estree "0.340.0" + flow-parser "0.340.0" string-width "4.2.3" for-each@^0.3.3: