From 5ff01bc1d2c6db6ed268ed57be5b989a69889e2a Mon Sep 17 00:00:00 2001 From: MaeIg Date: Mon, 27 Mar 2023 06:59:04 -0700 Subject: [PATCH] Extract getCommandTypeNameAndOptionsExpression from component folders into parsers-common (#36640) Summary: This PR aims to remove the duplicated logic in [flow|typescript]/components/index.js files to move it in parsers-commons. It is a task of https://github.com/facebook/react-native/issues/34872: > [Codegen 98 - assigned to MaeIg] Extract the namedExports.map(statement => ([Flow](https://github.com/facebook/react-native/blob/main/packages/react-native-codegen/src/parsers/flow/components/index.js#L76-L108), [TS](https://github.com/facebook/react-native/blob/main/packages/react-native-codegen/src/parsers/typescript/components/index.js#L77-L109)) function in parser-commons, so that it accept a Parser parameter to unify the behaviors between flow and typescript. The Parser object needs to be enriched with all the methods to extract the required information from the Node, if they are not there yet. ## Changelog [Internal] [Changed] - Extract getCommandTypeNameAndOptionsExpression from component folders into parsers-common Pull Request resolved: https://github.com/facebook/react-native/pull/36640 Test Plan: yarn flow: image yarn lint: image yarn test image Reviewed By: cipolleschi Differential Revision: D44416032 Pulled By: rshest fbshipit-source-id: eb682834d3da7a89661612667d9fc1df99ff3df0 --- .../parsers/__tests__/parsers-commons-test.js | 128 ++++++++++++++++++ .../src/parsers/__tests__/parsers-test.js | 26 ++++ .../src/parsers/flow/components/index.js | 36 +---- .../src/parsers/flow/parser.js | 4 + .../src/parsers/parser.js | 5 + .../src/parsers/parserMock.js | 4 + .../src/parsers/parsers-commons.js | 42 ++++++ .../parsers/typescript/components/index.js | 36 +---- .../src/parsers/typescript/parser.js | 4 + 9 files changed, 217 insertions(+), 68 deletions(-) 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,