From bd9d0e3e3b16cc93a9bd8d5149a2c1407081e01a Mon Sep 17 00:00:00 2001 From: George Zahariev Date: Thu, 12 Nov 2026 21:14:31 -0800 Subject: [PATCH] Upgrade the Flow parser tooling to 0.340.0 Summary: X-link: https://github.com/react/metro/pull/2224 @public Upgrade `flow-parser`, `flow-estree`, `flow-eslint`, `flow-transform` and `flow-api-translator` to 0.340.0, and adapt the code generators, codemods, lint rules and parser adapters that read the ESTree output those releases produce. Four shapes moved since the versions in use, and each one silently changed behavior rather than failing loudly: - Generic type annotations carry their arguments in `typeArguments` instead of `typeParameters`, so consumers that still read the old field dropped every type argument. Readers now take the canonical field whenever the node carries it, including when it is `null`, and fall back to the previous field otherwise, so the same source keeps working with the older releases that remain installed elsewhere. - Enum declarations expose a single `EnumBody` carrying an explicit type instead of one node type per value kind. Enum readers accept both and resolve the value kind the same way, so the supported enums and the existing rejection of boolean and symbol enums are unchanged. - `JSXAttribute.name` widened to also allow a namespaced name, so transforms that compare an attribute name against a list now match only plain identifiers and leave namespaced attributes alone, which is what they already did at runtime. - Object type properties, array elements, import phases and several other fields are now nullable or newly required. Readers guard them and report the existing unsupported-input errors instead of failing on a missing property, and import declarations are built from constant parser-owned templates so one source satisfies both the old and the new builder. The lint rules, the declaration generators and the `jscodeshift`/recast boundary are adapted the same way, and the published type mirrors for the lint and transform packages are refreshed. Changelog: [Internal] Reviewed By: SamChou19815, panagosg7 Differential Revision: D131489493 --- .eslintrc.js | 7 +++ package.json | 8 +-- .../eslint-plugin-react-native/package.json | 4 +- packages/eslint-plugin-specs/package.json | 4 +- .../react-native-babel-preset/package.json | 2 +- .../package.json | 2 +- packages/react-native-codegen/package.json | 4 +- .../src/parsers/__tests__/parsers-test.js | 61 +++++++++++++++++++ .../src/parsers/__tests__/utils-test.js | 49 +++++++++++++++ .../src/parsers/error-utils.js | 3 +- .../src/parsers/flow/components/commands.js | 3 +- .../flow/components/componentsUtils.js | 19 +++--- .../src/parsers/flow/components/events.js | 7 ++- .../src/parsers/flow/modules/index.js | 15 ++++- .../src/parsers/flow/parser.js | 19 +++--- .../src/parsers/flow/utils.js | 42 +++++++++++++ .../src/parsers/parsers-commons.js | 31 +++++----- .../src/parsers/parsers-primitives.js | 8 +-- .../react-native-codegen/src/parsers/utils.js | 13 ++++ packages/react-native/package.json | 2 +- yarn.lock | 54 +++++++++------- 21 files changed, 277 insertions(+), 80 deletions(-) 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: