Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint-plugin-react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint-plugin-specs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-babel-preset/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-babel-transformer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
4 changes: 2 additions & 2 deletions packages/react-native-codegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Spec>('NativeSampleTurboModule');
`,
'path/NativeSampleTurboModule.js',
),
).toThrow(/Flow type annotation 'Enum\w*Body' is unsupported/);
});
});
});

describe('TypeScriptParser', () => {
Expand Down
49 changes: 49 additions & 0 deletions packages/react-native-codegen/src/parsers/__tests__/utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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);
});
});
3 changes: 2 additions & 1 deletion packages/react-native-codegen/src/parsers/error-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const {
UntypedModuleRegistryCallParserError,
UnusedModuleInterfaceParserError,
} = require('./errors');
const {getTypeArguments} = require('./utils');

function throwIfModuleInterfaceIsMisnamed(
nativeModuleName: string,
Expand Down Expand Up @@ -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.');
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -114,7 +115,7 @@ function buildCommandSchema(
returnType = {
type: 'ArrayTypeAnnotation',
elementType: getCommandArrayElementTypeType(
paramValue.typeParameters.params[0],
getTypeArguments(paramValue).params[0],
parser,
),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -52,7 +53,7 @@ function getTypeAnnotationForArray<out T>(
return {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
objectType.typeParameters.params[0].properties,
getTypeArguments(objectType).params[0].properties,
types,
parser,
)
Expand All @@ -68,7 +69,7 @@ function getTypeAnnotationForArray<out T>(
// 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,
);

Expand All @@ -77,7 +78,7 @@ function getTypeAnnotationForArray<out T>(
elementType: {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
nestedObjectType.typeParameters.params[0].properties,
getTypeArguments(nestedObjectType).params[0].properties,
types,
parser,
)
Expand Down Expand Up @@ -239,7 +240,7 @@ function getTypeAnnotation<out T>(
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeParameters.params[0],
getTypeArguments(typeAnnotation).params[0],
defaultValue,
types,
parser,
Expand All @@ -256,7 +257,7 @@ function getTypeAnnotation<out T>(
return {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
typeAnnotation.typeParameters.params[0].properties,
getTypeArguments(typeAnnotation).params[0].properties,
types,
parser,
)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -66,7 +67,7 @@ function getPropertyType(
return getPropertyType(
name,
optional,
typeAnnotation.typeParameters.params[0],
getTypeArguments(typeAnnotation).params[0],
parser,
);
case 'ObjectTypeAnnotation':
Expand Down Expand Up @@ -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 ${
Expand Down Expand Up @@ -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,
};
Expand Down
Loading
Loading