Skip to content

Commit

Permalink
extract buildModuleSchema to parsers-commons (#36330)
Browse files Browse the repository at this point in the history
Summary:
> Extract the buildModuleSchema function ([Flow](https://github.com/facebook/react-native/blob/main/packages/react-native-codegen/src/parsers/flow/modules/index.js#L571), [TypeScript](https://github.com/facebook/react-native/blob/main/packages/react-native-codegen/src/parsers/typescript/modules/index.js#L584))in the parsers-commons.js function. The two functions are almost identical except for the filter(property =>) at the end of the function, which is different based on the language.

Part of Codegen Issue #34872
Depends on Codegen 74, Codegen 84.

## 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 buildModuleSchema from Flow and TypeScript parsers modules to parsers-commons

Pull Request resolved: #36330

Test Plan:
`yarn lint && yarn run flow && yarn test react-native-codegen
`

Reviewed By: christophpurrer

Differential Revision: D43833653

Pulled By: cipolleschi

fbshipit-source-id: 4d67cb5ef746ecd7ace4b91eb30d36e96252e779
  • Loading branch information
tarunrajput authored and facebook-github-bot committed Mar 10, 2023
1 parent e68f513 commit 3cd97e4
Show file tree
Hide file tree
Showing 7 changed files with 402 additions and 250 deletions.
38 changes: 32 additions & 6 deletions packages/eslint-plugin-specs/react-native-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,17 @@ const ERRORS = {
let RNModuleParser;
let RNParserUtils;
let RNFlowParser;
let RNParserCommons;
let RNFlowParserUtils;

function requireModuleParser() {
if (RNModuleParser == null || RNParserUtils == null || RNFlowParser == null) {
if (
RNModuleParser == null ||
RNParserUtils == null ||
RNFlowParser == null ||
RNParserCommons == null ||
RNFlowParserUtils == null
) {
// If using this externally, we leverage @react-native/codegen as published form
if (!PACKAGE_USAGE) {
const config = {
Expand All @@ -38,6 +46,8 @@ function requireModuleParser() {
RNModuleParser = require('@react-native/codegen/src/parsers/flow/modules');
RNParserUtils = require('@react-native/codegen/src/parsers/utils');
RNFlowParser = require('@react-native/codegen/src/parsers/flow/parser');
RNParserCommons = require('@react-native/codegen/src/parsers/parsers-commons');
RNFlowParserUtils = require('@react-native/codegen/src/parsers/flow/utils');
});
} else {
const config = {
Expand All @@ -47,16 +57,20 @@ function requireModuleParser() {

withBabelRegister(config, () => {
RNModuleParser = require('@react-native/codegen/lib/parsers/flow/modules');
RNParserUtils = require('@react-native/codegen/lib/parsers/flow/utils');
RNParserUtils = require('@react-native/codegen/lib/parsers/utils');
RNFlowParser = require('@react-native/codegen/lib/parsers/flow/parser');
RNParserCommons = require('@react-native/codegen/lib/parsers/parsers-commons');
RNFlowParserUtils = require('@react-native/codegen/lib/parsers/flow/utils');
});
}
}

return {
buildModuleSchema: RNModuleParser.buildModuleSchema,
buildModuleSchema: RNParserCommons.buildModuleSchema,
createParserErrorCapturer: RNParserUtils.createParserErrorCapturer,
parser: new RNFlowParser.FlowParser(),
resolveTypeAnnotation: RNFlowParserUtils.resolveTypeAnnotation,
translateTypeAnnotation: RNModuleParser.flowTranslateTypeAnnotation,
};
}

Expand Down Expand Up @@ -131,16 +145,28 @@ function rule(context) {
});
}

const {buildModuleSchema, createParserErrorCapturer, parser} =
requireModuleParser();
const {
buildModuleSchema,
createParserErrorCapturer,
parser,
resolveTypeAnnotation,
translateTypeAnnotation,
} = requireModuleParser();

const [parsingErrors, tryParse] = createParserErrorCapturer();

const sourceCode = context.getSourceCode().getText();
const ast = parser.getAst(sourceCode);

tryParse(() => {
buildModuleSchema(hasteModuleName, ast, tryParse, parser);
buildModuleSchema(
hasteModuleName,
ast,
tryParse,
parser,
resolveTypeAnnotation,
translateTypeAnnotation,
);
});

parsingErrors.forEach(error => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ import type {ParserType} from '../errors';
const {Visitor} = require('../flow/Visitor');
const {wrapComponentSchema} = require('../schema.js');
const {buildComponentSchema} = require('../flow/components');
const {buildModuleSchema} = require('../flow/modules');
const {isModuleRegistryCall} = require('../utils.js');
const {buildModuleSchema} = require('../parsers-commons.js');
const {
isModuleRegistryCall,
createParserErrorCapturer,
} = require('../utils.js');
const {
ParserError,
UnsupportedObjectPropertyTypeAnnotationParserError,
Expand All @@ -36,6 +39,9 @@ const {
IncorrectModuleRegistryCallArityParserError,
IncorrectModuleRegistryCallArgumentTypeParserError,
UntypedModuleRegistryCallParserError,
ModuleInterfaceNotFoundParserError,
MoreThanOneModuleInterfaceParserError,
MisnamedModuleInterfaceParserError,
} = require('../errors');

import {MockedParser} from '../parserMock';
Expand All @@ -45,8 +51,9 @@ const parser = new MockedParser();

const flowParser = new FlowParser();

const flowTranslateTypeAnnotation = require('../flow/modules/index');
const {flowTranslateTypeAnnotation} = require('../flow/modules/index');
const typeScriptTranslateTypeAnnotation = require('../typescript/modules/index');
const {resolveTypeAnnotation} = require('../flow/utils');

beforeEach(() => {
jest.clearAllMocks();
Expand Down Expand Up @@ -396,7 +403,9 @@ describe('buildSchemaFromConfigType', () => {
const buildComponentSchemaMock = jest.fn(
(_ast, _parser) => componentSchemaMock,
);
const buildModuleSchemaMock = jest.fn((_0, _1, _2, _3) => moduleSchemaMock);
const buildModuleSchemaMock = jest.fn(
(_0, _1, _2, _3, _4, _5) => moduleSchemaMock,
);

const buildSchemaFromConfigTypeHelper = (
configType: 'module' | 'component' | 'none',
Expand All @@ -410,6 +419,8 @@ describe('buildSchemaFromConfigType', () => {
buildComponentSchemaMock,
buildModuleSchemaMock,
parser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
);

describe('when configType is none', () => {
Expand Down Expand Up @@ -491,6 +502,8 @@ describe('buildSchemaFromConfigType', () => {
astMock,
expect.any(Function),
parser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
);

expect(buildComponentSchemaMock).not.toHaveBeenCalled();
Expand Down Expand Up @@ -661,6 +674,8 @@ describe('buildSchema', () => {
buildModuleSchema,
Visitor,
parser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
);

expect(getConfigTypeSpy).not.toHaveBeenCalled();
Expand Down Expand Up @@ -693,6 +708,8 @@ describe('buildSchema', () => {
buildModuleSchema,
Visitor,
flowParser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
);

expect(getConfigTypeSpy).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -746,6 +763,8 @@ describe('buildSchema', () => {
buildModuleSchema,
Visitor,
flowParser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
);

expect(getConfigTypeSpy).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -1007,3 +1026,208 @@ describe('parseModuleName', () => {
});
});
});

describe('buildModuleSchema', () => {
const hasteModuleName = 'TestModuleName';
const [, tryParse] = createParserErrorCapturer();
const language = flowParser.language();
const NATIVE_MODULE = `
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getArray: (a: Array<any>) => Array<string>;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
'SampleTurboModule',
): Spec);
`;

describe('throwIfModuleInterfaceNotFound', () => {
it('should throw ModuleInterfaceNotFoundParserError if no module interface is found', () => {
const ast = flowParser.getAst('');
const expected = new ModuleInterfaceNotFoundParserError(
hasteModuleName,
ast,
language,
);

expect(() =>
buildModuleSchema(
hasteModuleName,
ast,
tryParse,
flowParser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
),
).toThrow(expected);
});

it('should not throw ModuleInterfaceNotFoundParserError if module interface is found', () => {
const ast = flowParser.getAst(NATIVE_MODULE);

expect(() =>
buildModuleSchema(
hasteModuleName,
ast,
tryParse,
flowParser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
),
).not.toThrow();
});
});

describe('throwIfMoreThanOneModuleInterfaceParser', () => {
it('should throw an error if mulitple module interfaces are found', () => {
const contents = `
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getBool: (arg: boolean) => boolean; }
export interface SpecOther extends TurboModule {
+getArray: (a: Array<any>) => Array<string>;
}
`;
const ast = flowParser.getAst(contents);
const types = flowParser.getTypes(ast);
const moduleSpecs = Object.values(types).filter(t =>
flowParser.isModuleInterface(t),
);
const expected = new MoreThanOneModuleInterfaceParserError(
hasteModuleName,
moduleSpecs,
moduleSpecs.map(node => node.id.name),
language,
);

expect(() =>
buildModuleSchema(
hasteModuleName,
ast,
tryParse,
flowParser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
),
).toThrow(expected);
});

it('should not throw an error if exactly one module interface is found', () => {
const ast = flowParser.getAst(NATIVE_MODULE);

expect(() =>
buildModuleSchema(
hasteModuleName,
ast,
tryParse,
flowParser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
),
).not.toThrow();
});
});

describe('throwIfModuleInterfaceIsMisnamed', () => {
it('should throw an error if module interface is misnamed', () => {
const contents = `
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
export interface MisnamedSpec extends TurboModule {
+getArray: (a: Array<any>) => Array<string>;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
'SampleTurboModule',
): Spec);
`;
const ast = flowParser.getAst(contents);
const types = flowParser.getTypes(ast);
const moduleSpecs = Object.values(types).filter(t =>
flowParser.isModuleInterface(t),
);
const [moduleSpec] = moduleSpecs;

const expected = new MisnamedModuleInterfaceParserError(
hasteModuleName,
moduleSpec.id,
language,
);

expect(() =>
buildModuleSchema(
hasteModuleName,
ast,
tryParse,
flowParser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
),
).toThrow(expected);
});

it('should not throw an error if module interface is correctly named', () => {
const ast = flowParser.getAst(NATIVE_MODULE);

expect(() =>
buildModuleSchema(
hasteModuleName,
ast,
tryParse,
flowParser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
),
).not.toThrow();
});
});

it('should return valid module schema', () => {
const ast = flowParser.getAst(NATIVE_MODULE);
const schmeaMock = {
aliasMap: {},
enumMap: {},
excludedPlatforms: undefined,
moduleName: 'SampleTurboModule',
spec: {
properties: [
{
name: 'getArray',
optional: false,
typeAnnotation: {
params: [
{
name: 'a',
optional: false,
typeAnnotation: {type: 'ArrayTypeAnnotation'},
},
],
returnTypeAnnotation: {
elementType: {type: 'StringTypeAnnotation'},
type: 'ArrayTypeAnnotation',
},
type: 'FunctionTypeAnnotation',
},
},
],
},
type: 'NativeModule',
};
const schema = buildModuleSchema(
hasteModuleName,
ast,
tryParse,
flowParser,
resolveTypeAnnotation,
flowTranslateTypeAnnotation,
);

expect(schema).toEqual(schmeaMock);
});
});
Loading

0 comments on commit 3cd97e4

Please sign in to comment.