diff --git a/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js b/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js index 54217fc11bd..dc16fad3e1d 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js @@ -23,6 +23,7 @@ import { createComponentConfig, getCommandOptions, getOptions, + getCommandTypeNameAndOptionsExpression, } from '../parsers-commons'; import type {ParserType} from '../errors'; @@ -1409,3 +1410,130 @@ describe('getOptions', () => { expect(getOptions(optionsExpression)).toEqual(expectedOptions); }); }); + +describe('getCommandTypeNameAndOptionsExpression', () => { + it("returns undefined when namedExport isn't well formatted", () => { + expect( + getCommandTypeNameAndOptionsExpression(null, flowParser), + ).toBeUndefined(); + + expect( + getCommandTypeNameAndOptionsExpression(undefined, flowParser), + ).toBeUndefined(); + + expect( + getCommandTypeNameAndOptionsExpression({}, flowParser), + ).toBeUndefined(); + }); + + it('returns undefined when the called expression name is not codegenNativeCommands', () => { + const namedExportMock = { + declaration: { + declarations: [ + { + init: { + callee: { + name: 'notCodegenNativeCommands', + }, + }, + }, + ], + }, + }; + + expect( + getCommandTypeNameAndOptionsExpression(namedExportMock, flowParser), + ).toBeUndefined(); + }); + + it("throws when the called expression doesn't have 1 argument", () => { + const namedExportMock = { + declaration: { + declarations: [ + { + init: { + callee: { + name: 'codegenNativeCommands', + }, + arguments: [], + }, + }, + ], + }, + }; + + expect(() => + getCommandTypeNameAndOptionsExpression(namedExportMock, flowParser), + ).toThrow( + new Error( + 'codegenNativeCommands must be passed options including the supported commands', + ), + ); + }); + + it('throws when the type of the argument is not a generic type annotation', () => { + const namedExportMock = { + declaration: { + declarations: [ + { + init: { + callee: { + name: 'codegenNativeCommands', + }, + arguments: [{}], + typeArguments: {params: [{type: 'StringTypeAnnotation'}]}, + }, + }, + ], + }, + }; + + expect(() => + getCommandTypeNameAndOptionsExpression(namedExportMock, flowParser), + ).toThrow( + new Error( + "codegenNativeCommands doesn't support inline definitions. Specify a file local type alias", + ), + ); + }); + + it('returns the command TypeName and options expression when the named export is valid', () => { + const commandTypeName = 'MyCommandType'; + const commandOptionsExpression = { + type: 'ObjectExpression', + properties: [], + }; + + const namedExportMock = { + declaration: { + declarations: [ + { + init: { + callee: { + name: 'codegenNativeCommands', + }, + arguments: [commandOptionsExpression], + typeArguments: { + params: [ + { + type: 'GenericTypeAnnotation', + id: { + name: commandTypeName, + }, + }, + ], + }, + }, + }, + ], + }, + }; + + expect( + getCommandTypeNameAndOptionsExpression(namedExportMock, flowParser), + ).toStrictEqual({ + commandTypeName, + commandOptionsExpression, + }); + }); +}); 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 43773f0fdc8..9418c0539e8 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-test.js @@ -90,6 +90,20 @@ describe('FlowParser', () => { }); }); + describe('isGenericTypeAnnotation', () => { + it('returns true if it is a generic type annotation', () => { + expect(parser.isGenericTypeAnnotation('GenericTypeAnnotation')).toBe( + true, + ); + }); + + it('returns false if it is not a generic type annotation', () => { + expect(parser.isGenericTypeAnnotation('StringTypeAnnotation')).toBe( + false, + ); + }); + }); + describe('callExpressionTypeParameters', () => { it('returns type arguments if it is a valid node', () => { const node = { @@ -328,6 +342,18 @@ describe('TypeScriptParser', () => { }); }); + describe('isGenericTypeAnnotation', () => { + it('returns true if it is a generic type annotation', () => { + expect(parser.isGenericTypeAnnotation('TSTypeReference')).toBe(true); + }); + + it('returns false if it is not a generic type annotation', () => { + expect(parser.isGenericTypeAnnotation('StringTypeAnnotation')).toBe( + false, + ); + }); + }); + describe('callExpressionTypeParameters', () => { it('returns type parameters if it is a valid node', () => { const node = { diff --git a/packages/react-native-codegen/src/parsers/flow/components/index.js b/packages/react-native-codegen/src/parsers/flow/components/index.js index 43aee568045..b5d55e2702c 100644 --- a/packages/react-native-codegen/src/parsers/flow/components/index.js +++ b/packages/react-native-codegen/src/parsers/flow/components/index.js @@ -25,6 +25,7 @@ const { findNativeComponentType, getCommandOptions, getOptions, + getCommandTypeNameAndOptionsExpression, } = require('../../parsers-commons'); // $FlowFixMe[signature-verification-failure] there's no flowtype for AST @@ -53,40 +54,7 @@ function findComponentConfig(ast: $FlowFixMe, parser: Parser) { ); const commandsTypeNames = namedExports - .map(statement => { - let callExpression; - let calleeName; - try { - callExpression = statement.declaration.declarations[0].init; - calleeName = callExpression.callee.name; - } catch (e) { - return; - } - - if (calleeName !== 'codegenNativeCommands') { - return; - } - - // const statement.declaration.declarations[0].init - if (callExpression.arguments.length !== 1) { - throw new Error( - 'codegenNativeCommands must be passed options including the supported commands', - ); - } - - const typeArgumentParam = callExpression.typeArguments.params[0]; - - if (typeArgumentParam.type !== 'GenericTypeAnnotation') { - throw new Error( - "codegenNativeCommands doesn't support inline definitions. Specify a file local type alias", - ); - } - - return { - commandTypeName: typeArgumentParam.id.name, - commandOptionsExpression: callExpression.arguments[0], - }; - }) + .map(statement => getCommandTypeNameAndOptionsExpression(statement, parser)) .filter(Boolean); throwIfMoreThanOneCodegenNativecommands(commandsTypeNames); diff --git a/packages/react-native-codegen/src/parsers/flow/parser.js b/packages/react-native-codegen/src/parsers/flow/parser.js index f82a52dc18d..2e5da42e134 100644 --- a/packages/react-native-codegen/src/parsers/flow/parser.js +++ b/packages/react-native-codegen/src/parsers/flow/parser.js @@ -214,6 +214,10 @@ class FlowParser implements Parser { ); } + isGenericTypeAnnotation(type: $FlowFixMe): boolean { + return type === 'GenericTypeAnnotation'; + } + extractAnnotatedElement( typeAnnotation: $FlowFixMe, types: TypeDeclarationMap, diff --git a/packages/react-native-codegen/src/parsers/parser.js b/packages/react-native-codegen/src/parsers/parser.js index fc1777bda93..78d10ba141a 100644 --- a/packages/react-native-codegen/src/parsers/parser.js +++ b/packages/react-native-codegen/src/parsers/parser.js @@ -161,6 +161,11 @@ export interface Parser { */ isModuleInterface(node: $FlowFixMe): boolean; + /** + * Given a type name, it returns true if it is a generic type annotation + */ + isGenericTypeAnnotation(type: $FlowFixMe): boolean; + /** * Given a typeAnnotation, it returns the annotated element. * @parameter typeAnnotation: the annotation for a type. diff --git a/packages/react-native-codegen/src/parsers/parserMock.js b/packages/react-native-codegen/src/parsers/parserMock.js index 81c88c7e6cb..2db705cdba5 100644 --- a/packages/react-native-codegen/src/parsers/parserMock.js +++ b/packages/react-native-codegen/src/parsers/parserMock.js @@ -172,6 +172,10 @@ export class MockedParser implements Parser { ); } + isGenericTypeAnnotation(type: $FlowFixMe): boolean { + return true; + } + extractAnnotatedElement( typeAnnotation: $FlowFixMe, types: TypeDeclarationMap, diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index 1a8d3025503..32361f7c40e 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -769,6 +769,47 @@ function getOptions(optionsExpression: OptionsAST): ?OptionsShape { return foundOptions; } +function getCommandTypeNameAndOptionsExpression( + namedExport: $FlowFixMe, + parser: Parser, +): { + commandOptionsExpression: OptionsAST, + commandTypeName: string, +} | void { + let callExpression; + let calleeName; + try { + callExpression = namedExport.declaration.declarations[0].init; + calleeName = callExpression.callee.name; + } catch (e) { + return; + } + + if (calleeName !== 'codegenNativeCommands') { + return; + } + + if (callExpression.arguments.length !== 1) { + throw new Error( + 'codegenNativeCommands must be passed options including the supported commands', + ); + } + + const typeArgumentParam = + parser.getTypeArgumentParamsFromDeclaration(callExpression)[0]; + + if (!parser.isGenericTypeAnnotation(typeArgumentParam.type)) { + throw new Error( + "codegenNativeCommands doesn't support inline definitions. Specify a file local type alias", + ); + } + + return { + commandTypeName: parser.nameForGenericTypeAnnotation(typeArgumentParam), + commandOptionsExpression: callExpression.arguments[0], + }; +} + module.exports = { wrapModuleSchema, unwrapNullable, @@ -786,4 +827,5 @@ module.exports = { findNativeComponentType, getCommandOptions, getOptions, + getCommandTypeNameAndOptionsExpression, }; diff --git a/packages/react-native-codegen/src/parsers/typescript/components/index.js b/packages/react-native-codegen/src/parsers/typescript/components/index.js index 4c41d7f1fde..63270f66fd9 100644 --- a/packages/react-native-codegen/src/parsers/typescript/components/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/components/index.js @@ -26,6 +26,7 @@ const { findNativeComponentType, getCommandOptions, getOptions, + getCommandTypeNameAndOptionsExpression, } = require('../../parsers-commons'); // $FlowFixMe[signature-verification-failure] TODO(T108222691): Use flow-types for @babel/parser @@ -54,40 +55,7 @@ function findComponentConfig(ast: $FlowFixMe, parser: Parser) { ); const commandsTypeNames = namedExports - .map(statement => { - let callExpression; - let calleeName; - try { - callExpression = statement.declaration.declarations[0].init; - calleeName = callExpression.callee.name; - } catch (e) { - return; - } - - if (calleeName !== 'codegenNativeCommands') { - return; - } - - // const statement.declaration.declarations[0].init - if (callExpression.arguments.length !== 1) { - throw new Error( - 'codegenNativeCommands must be passed options including the supported commands', - ); - } - - const typeArgumentParam = callExpression.typeParameters.params[0]; - - if (typeArgumentParam.type !== 'TSTypeReference') { - throw new Error( - "codegenNativeCommands doesn't support inline definitions. Specify a file local type alias", - ); - } - - return { - commandTypeName: typeArgumentParam.typeName.name, - commandOptionsExpression: callExpression.arguments[0], - }; - }) + .map(statement => getCommandTypeNameAndOptionsExpression(statement, parser)) .filter(Boolean); throwIfMoreThanOneCodegenNativecommands(commandsTypeNames); diff --git a/packages/react-native-codegen/src/parsers/typescript/parser.js b/packages/react-native-codegen/src/parsers/typescript/parser.js index e5dd58f80a4..1e22da0f1ab 100644 --- a/packages/react-native-codegen/src/parsers/typescript/parser.js +++ b/packages/react-native-codegen/src/parsers/typescript/parser.js @@ -210,6 +210,10 @@ class TypeScriptParser implements Parser { ); } + isGenericTypeAnnotation(type: $FlowFixMe): boolean { + return type === 'TSTypeReference'; + } + extractAnnotatedElement( typeAnnotation: $FlowFixMe, types: TypeDeclarationMap,