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 047bb00d8a..bd62076f6e 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 @@ -25,6 +25,8 @@ import { getCommandOptions, getOptions, getCommandTypeNameAndOptionsExpression, + getTypeResolutionStatus, + handleGenericTypeAnnotation, } from '../parsers-commons'; import type {ParserType} from '../errors'; @@ -1544,3 +1546,120 @@ describe('getCommandTypeNameAndOptionsExpression', () => { }); }); }); + +describe('getTypeResolutionStatus', () => { + it('returns type resolution status for a type declaration', () => { + const typeAnnotation = { + id: { + name: 'TypeAnnotationName', + }, + }; + expect( + getTypeResolutionStatus('alias', typeAnnotation, flowParser), + ).toEqual({ + successful: true, + type: 'alias', + name: 'TypeAnnotationName', + }); + }); + + it('returns type resolution status for an enum declaration', () => { + const typeAnnotation = { + id: { + name: 'TypeAnnotationName', + }, + }; + expect(getTypeResolutionStatus('enum', typeAnnotation, flowParser)).toEqual( + { + successful: true, + type: 'enum', + name: 'TypeAnnotationName', + }, + ); + }); +}); + +describe('handleGenericTypeAnnotation', () => { + it('returns when TypeAnnotation is a type declaration', () => { + const typeAnnotation = { + id: { + name: 'TypeAnnotationName', + }, + }; + const resolvedTypeAnnotation = { + type: 'TypeAlias', + right: { + type: 'TypeAnnotation', + }, + }; + expect( + handleGenericTypeAnnotation( + typeAnnotation, + resolvedTypeAnnotation, + flowParser, + ), + ).toEqual({ + typeAnnotation: { + type: 'TypeAnnotation', + }, + typeResolutionStatus: { + successful: true, + type: 'alias', + name: 'TypeAnnotationName', + }, + }); + }); + + it('returns when TypeAnnotation is an enum declaration', () => { + const typeAnnotation = { + id: { + name: 'TypeAnnotationName', + }, + }; + const resolvedTypeAnnotation = { + type: 'EnumDeclaration', + body: { + type: 'TypeAnnotation', + }, + }; + expect( + handleGenericTypeAnnotation( + typeAnnotation, + resolvedTypeAnnotation, + flowParser, + ), + ).toEqual({ + typeAnnotation: { + type: 'TypeAnnotation', + }, + typeResolutionStatus: { + successful: true, + type: 'enum', + name: 'TypeAnnotationName', + }, + }); + }); + + it('throws when the non GenericTypeAnnotation is unsupported', () => { + const typeAnnotation = { + type: 'UnsupportedTypeAnnotation', + id: { + name: 'UnsupportedType', + }, + }; + const resolvedTypeAnnotation = { + type: 'UnsupportedTypeAnnotation', + }; + expect(() => + handleGenericTypeAnnotation( + typeAnnotation, + resolvedTypeAnnotation, + flowParser, + ), + ).toThrow( + new Error( + parser.genericTypeAnnotationErrorMessage(resolvedTypeAnnotation), + ), + ); + }); +}); diff --git a/packages/react-native-codegen/src/parsers/flow/parser.js b/packages/react-native-codegen/src/parsers/flow/parser.js index ef7dc90227..02a02e49b3 100644 --- a/packages/react-native-codegen/src/parsers/flow/parser.js +++ b/packages/react-native-codegen/src/parsers/flow/parser.js @@ -55,11 +55,15 @@ const {flowTranslateTypeAnnotation} = require('./modules'); // $FlowFixMe[untyped-import] there's no flowtype flow-parser const flowParser = require('flow-parser'); -const {buildSchema, buildPropSchema} = require('../parsers-commons'); +const { + buildSchema, + buildPropSchema, + buildModuleSchema, + handleGenericTypeAnnotation, +} = require('../parsers-commons'); const {Visitor} = require('../parsers-primitives'); const {buildComponentSchema} = require('./components'); const {wrapComponentSchema} = require('../schema.js'); -const {buildModuleSchema} = require('../parsers-commons.js'); const fs = require('fs'); @@ -411,36 +415,16 @@ class FlowParser implements Parser { break; } - const resolvedTypeAnnotation = types[node.id.name]; + const typeAnnotationName = this.nameForGenericTypeAnnotation(node); + const resolvedTypeAnnotation = types[typeAnnotationName]; if (resolvedTypeAnnotation == null) { break; } - switch (resolvedTypeAnnotation.type) { - case parser.typeAlias: { - typeResolutionStatus = { - successful: true, - type: 'alias', - name: node.id.name, - }; - node = resolvedTypeAnnotation.right; - break; - } - case parser.enumDeclaration: { - typeResolutionStatus = { - successful: true, - type: 'enum', - name: node.id.name, - }; - node = resolvedTypeAnnotation.body; - break; - } - default: { - throw new TypeError( - `A non GenericTypeAnnotation must be a type declaration ('${parser.typeAlias}') or enum ('${parser.enumDeclaration}'). Instead, got the unsupported ${resolvedTypeAnnotation.type}.`, - ); - } - } + const {typeAnnotation: typeAnnotationNode, typeResolutionStatus: status} = + handleGenericTypeAnnotation(node, resolvedTypeAnnotation, this); + typeResolutionStatus = status; + node = typeAnnotationNode; } return { @@ -531,6 +515,18 @@ class FlowParser implements Parser { ); } } + + nextNodeForTypeAlias(typeAnnotation: $FlowFixMe): $FlowFixMe { + return typeAnnotation.right; + } + + nextNodeForEnum(typeAnnotation: $FlowFixMe): $FlowFixMe { + return typeAnnotation.body; + } + + genericTypeAnnotationErrorMessage(typeAnnotation: $FlowFixMe): string { + return `A non GenericTypeAnnotation must be a type declaration ('${this.typeAlias}') or enum ('${this.enumDeclaration}'). Instead, got the unsupported ${typeAnnotation.type}.`; + } } module.exports = { diff --git a/packages/react-native-codegen/src/parsers/parser.js b/packages/react-native-codegen/src/parsers/parser.js index 79fe566e55..77a6243736 100644 --- a/packages/react-native-codegen/src/parsers/parser.js +++ b/packages/react-native-codegen/src/parsers/parser.js @@ -378,4 +378,19 @@ export interface Parser { }; getProperties(typeName: string, types: TypeDeclarationMap): $FlowFixMe; + + /** + * Given a typeAlias, it returns the next node. + */ + nextNodeForTypeAlias(typeAnnotation: $FlowFixMe): $FlowFixMe; + + /** + * Given an enum Declaration, it returns the next node. + */ + nextNodeForEnum(typeAnnotation: $FlowFixMe): $FlowFixMe; + + /** + * Given a unsupported typeAnnotation, returns an error message. + */ + genericTypeAnnotationErrorMessage(typeAnnotation: $FlowFixMe): string; } diff --git a/packages/react-native-codegen/src/parsers/parserMock.js b/packages/react-native-codegen/src/parsers/parserMock.js index 45a1aa0429..cc936dc185 100644 --- a/packages/react-native-codegen/src/parsers/parserMock.js +++ b/packages/react-native-codegen/src/parsers/parserMock.js @@ -456,4 +456,16 @@ export class MockedParser implements Parser { ); } } + + nextNodeForTypeAlias(typeAnnotation: $FlowFixMe): $FlowFixMe { + return typeAnnotation.right; + } + + nextNodeForEnum(typeAnnotation: $FlowFixMe): $FlowFixMe { + return typeAnnotation.body; + } + + genericTypeAnnotationErrorMessage(typeAnnotation: $FlowFixMe): string { + return `A non GenericTypeAnnotation must be a type declaration ('${this.typeAlias}') or enum ('${this.enumDeclaration}'). Instead, got the unsupported ${typeAnnotation.type}.`; + } } diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index 1a7112d61d..41f1353294 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -30,7 +30,12 @@ import type { import type {Parser} from './parser'; import type {ParserType} from './errors'; -import type {ParserErrorCapturer, TypeDeclarationMap, PropAST} from './utils'; +import type { + ParserErrorCapturer, + TypeDeclarationMap, + PropAST, + TypeResolutionStatus, +} from './utils'; import type {ComponentSchemaBuilderConfig} from './schema.js'; const { @@ -983,6 +988,71 @@ function getCommandProperties(ast: $FlowFixMe, parser: Parser) { return properties; } +function getTypeResolutionStatus( + type: 'alias' | 'enum', + typeAnnotation: $FlowFixMe, + parser: Parser, +): TypeResolutionStatus { + return { + successful: true, + type, + name: parser.nameForGenericTypeAnnotation(typeAnnotation), + }; +} + +function handleGenericTypeAnnotation( + typeAnnotation: $FlowFixMe, + resolvedTypeAnnotation: TypeDeclarationMap, + parser: Parser, +): { + typeAnnotation: $FlowFixMe, + typeResolutionStatus: TypeResolutionStatus, +} { + let typeResolutionStatus; + let node; + + switch (resolvedTypeAnnotation.type) { + case parser.typeAlias: { + typeResolutionStatus = getTypeResolutionStatus( + 'alias', + typeAnnotation, + parser, + ); + node = parser.nextNodeForTypeAlias(resolvedTypeAnnotation); + break; + } + case parser.enumDeclaration: { + typeResolutionStatus = getTypeResolutionStatus( + 'enum', + typeAnnotation, + parser, + ); + node = parser.nextNodeForEnum(resolvedTypeAnnotation); + break; + } + // parser.interfaceDeclaration is not used here because for flow it should fall through to default case and throw an error + case 'TSInterfaceDeclaration': { + typeResolutionStatus = getTypeResolutionStatus( + 'alias', + typeAnnotation, + parser, + ); + node = resolvedTypeAnnotation; + break; + } + default: { + throw new TypeError( + parser.genericTypeAnnotationErrorMessage(resolvedTypeAnnotation), + ); + } + } + + return { + typeAnnotation: node, + typeResolutionStatus, + }; +} + module.exports = { wrapModuleSchema, unwrapNullable, @@ -1007,4 +1077,6 @@ module.exports = { getEventArgument, findComponentConfig, getCommandProperties, + handleGenericTypeAnnotation, + getTypeResolutionStatus, }; diff --git a/packages/react-native-codegen/src/parsers/typescript/parser.js b/packages/react-native-codegen/src/parsers/typescript/parser.js index f75e6f5cc7..ab46d18121 100644 --- a/packages/react-native-codegen/src/parsers/typescript/parser.js +++ b/packages/react-native-codegen/src/parsers/typescript/parser.js @@ -43,14 +43,15 @@ const {typeScriptTranslateTypeAnnotation} = require('./modules'); // $FlowFixMe[untyped-import] Use flow-types for @babel/parser const babelParser = require('@babel/parser'); -const {buildSchema} = require('../parsers-commons'); const {Visitor} = require('../parsers-primitives'); const {buildComponentSchema} = require('./components'); const {wrapComponentSchema} = require('../schema.js'); const { + buildSchema, buildModuleSchema, extendsForProp, buildPropSchema, + handleGenericTypeAnnotation, } = require('../parsers-commons.js'); const {parseTopLevelType} = require('./parseTopLevelType'); @@ -408,45 +409,16 @@ class TypeScriptParser implements Parser { break; } - const resolvedTypeAnnotation = types[node.typeName.name]; + const typeAnnotationName = this.nameForGenericTypeAnnotation(node); + const resolvedTypeAnnotation = types[typeAnnotationName]; if (resolvedTypeAnnotation == null) { break; } - switch (resolvedTypeAnnotation.type) { - case parser.typeAlias: { - typeResolutionStatus = { - successful: true, - type: 'alias', - name: node.typeName.name, - }; - node = resolvedTypeAnnotation.typeAnnotation; - break; - } - case parser.interfaceDeclaration: { - typeResolutionStatus = { - successful: true, - type: 'alias', - name: node.typeName.name, - }; - node = resolvedTypeAnnotation; - break; - } - case parser.enumDeclaration: { - typeResolutionStatus = { - successful: true, - type: 'enum', - name: node.typeName.name, - }; - node = resolvedTypeAnnotation; - break; - } - default: { - throw new TypeError( - `A non GenericTypeAnnotation must be a type declaration ('${parser.typeAlias}'), an interface ('${parser.interfaceDeclaration}'), or enum ('${parser.enumDeclaration}'). Instead, got the unsupported ${resolvedTypeAnnotation.type}.`, - ); - } - } + const {typeAnnotation: typeAnnotationNode, typeResolutionStatus: status} = + handleGenericTypeAnnotation(node, resolvedTypeAnnotation, this); + typeResolutionStatus = status; + node = typeAnnotationNode; } return { @@ -559,6 +531,18 @@ class TypeScriptParser implements Parser { ); } } + + nextNodeForTypeAlias(typeAnnotation: $FlowFixMe): $FlowFixMe { + return typeAnnotation.typeAnnotation; + } + + nextNodeForEnum(typeAnnotation: $FlowFixMe): $FlowFixMe { + return typeAnnotation; + } + + genericTypeAnnotationErrorMessage(typeAnnotation: $FlowFixMe): string { + return `A non GenericTypeAnnotation must be a type declaration ('${this.typeAlias}'), an interface ('${this.interfaceDeclaration}'), or enum ('${this.enumDeclaration}'). Instead, got the unsupported ${typeAnnotation.type}.`; + } } module.exports = {