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

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->
[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:
<img width="151" alt="image" src="https://user-images.githubusercontent.com/40902940/227719831-1a67f588-3bb2-48d7-8a43-4c5c8972155e.png">

yarn lint:
<img width="499" alt="image" src="https://user-images.githubusercontent.com/40902940/227719839-e5b591c3-9f3a-4da4-b3a5-67275c58584f.png">

yarn test
<img width="389" alt="image" src="https://user-images.githubusercontent.com/40902940/227719849-f3adfc4a-9215-4b1a-8807-c01801c54628.png">

Reviewed By: cipolleschi

Differential Revision: D44416032

Pulled By: rshest

fbshipit-source-id: eb682834d3da7a89661612667d9fc1df99ff3df0
This commit is contained in:
MaeIg
2023-03-27 06:59:04 -07:00
committed by Facebook GitHub Bot
parent 92b8981499
commit 5ff01bc1d2
9 changed files with 217 additions and 68 deletions
@@ -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,
});
});
});
@@ -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 = {
@@ -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);
@@ -214,6 +214,10 @@ class FlowParser implements Parser {
);
}
isGenericTypeAnnotation(type: $FlowFixMe): boolean {
return type === 'GenericTypeAnnotation';
}
extractAnnotatedElement(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
+5
View File
@@ -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.
@@ -172,6 +172,10 @@ export class MockedParser implements Parser {
);
}
isGenericTypeAnnotation(type: $FlowFixMe): boolean {
return true;
}
extractAnnotatedElement(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
@@ -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,
};
@@ -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);
@@ -210,6 +210,10 @@ class TypeScriptParser implements Parser {
);
}
isGenericTypeAnnotation(type: $FlowFixMe): boolean {
return type === 'TSTypeReference';
}
extractAnnotatedElement(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,