first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function compareSnaps(
flowFixtures /*: $ReadOnly<{[string]: string}> */,
flowSnaps /*: $ReadOnly<{[string]: string}> */,
flowExtraCases /*: $ReadOnlyArray<string> */,
tsFixtures /*: $ReadOnly<{[string]: string}> */,
tsSnaps /*: $ReadOnly<{[string]: string}> */,
tsExtraCases /*: $ReadOnlyArray<string> */,
ignoredCases /*: $ReadOnlyArray<string> */,
) {
const flowCases = Object.keys(flowFixtures).sort();
const tsCases = Object.keys(tsFixtures).sort();
const commonCases = flowCases.filter(name => tsCases.indexOf(name) !== -1);
describe('RN Codegen Parsers', () => {
it('should not unintentionally contains test case for Flow but not for TypeScript', () => {
expect(
flowCases.filter(name => commonCases.indexOf(name) === -1),
).toEqual(flowExtraCases);
});
it('should not unintentionally contains test case for TypeScript but not for Flow', () => {
expect(tsCases.filter(name => commonCases.indexOf(name) === -1)).toEqual(
tsExtraCases,
);
});
for (const commonCase of commonCases) {
const flowSnap =
flowSnaps[
`RN Codegen Flow Parser can generate fixture ${commonCase} 1`
];
const tsSnap =
tsSnaps[
`RN Codegen TypeScript Parser can generate fixture ${commonCase} 1`
];
it(`should be able to find the snapshot for Flow for case ${commonCase}`, () => {
expect(typeof flowSnap).toEqual('string');
});
it(`should be able to find the snapshot for TypeScript for case ${commonCase}`, () => {
expect(typeof tsSnap).toEqual('string');
});
if (ignoredCases.indexOf(commonCase) === -1) {
it(`should generate the same snapshot from Flow and TypeScript for fixture ${commonCase}`, () => {
expect(flowSnap).toEqual(tsSnap);
});
} else {
it(`should generate the different snapshot from Flow and TypeScript for fixture ${commonCase}`, () => {
expect(flowSnap).not.toEqual(tsSnap);
});
}
}
});
}
function compareTsArraySnaps(
tsSnaps /*: $ReadOnly<{[string]: string}> */,
tsExtraCases /*: $ReadOnlyArray<string> */,
) {
for (const array2Case of tsExtraCases.filter(
name => name.indexOf('ARRAY2') !== -1,
)) {
const arrayCase = array2Case.replace('ARRAY2', 'ARRAY');
it(`should generate the same snap from fixture ${arrayCase} and ${array2Case}`, () => {
expect(
tsSnaps[
`RN Codegen TypeScript Parser can generate fixture ${arrayCase}`
],
).toEqual(
tsSnaps[
`RN Codegen TypeScript Parser can generate fixture ${array2Case}`
],
);
});
}
}
module.exports = {
compareSnaps,
compareTsArraySnaps,
};

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
function compareSnaps(
flowFixtures /*: $ReadOnly<{[string]: string}> */,
flowSnaps /*: $ReadOnly<{[string]: string}> */,
flowExtraCases /*: $ReadOnlyArray<string> */,
tsFixtures /*: $ReadOnly<{[string]: string}> */,
tsSnaps /*: $ReadOnly<{[string]: string}> */,
tsExtraCases /*: $ReadOnlyArray<string> */,
ignoredCases /*: $ReadOnlyArray<string> */,
) {
const flowCases = Object.keys(flowFixtures).sort();
const tsCases = Object.keys(tsFixtures).sort();
const commonCases = flowCases.filter(name => tsCases.indexOf(name) !== -1);
describe('RN Codegen Parsers', () => {
it('should not unintentionally contains test case for Flow but not for TypeScript', () => {
expect(
flowCases.filter(name => commonCases.indexOf(name) === -1),
).toEqual(flowExtraCases);
});
it('should not unintentionally contains test case for TypeScript but not for Flow', () => {
expect(tsCases.filter(name => commonCases.indexOf(name) === -1)).toEqual(
tsExtraCases,
);
});
for (const commonCase of commonCases) {
const flowSnap =
flowSnaps[
`RN Codegen Flow Parser can generate fixture ${commonCase} 1`
];
const tsSnap =
tsSnaps[
`RN Codegen TypeScript Parser can generate fixture ${commonCase} 1`
];
it(`should be able to find the snapshot for Flow for case ${commonCase}`, () => {
expect(typeof flowSnap).toEqual('string');
});
it(`should be able to find the snapshot for TypeScript for case ${commonCase}`, () => {
expect(typeof tsSnap).toEqual('string');
});
if (ignoredCases.indexOf(commonCase) === -1) {
it(`should generate the same snapshot from Flow and TypeScript for fixture ${commonCase}`, () => {
expect(flowSnap).toEqual(tsSnap);
});
} else {
it(`should generate the different snapshot from Flow and TypeScript for fixture ${commonCase}`, () => {
expect(flowSnap).not.toEqual(tsSnap);
});
}
}
});
}
function compareTsArraySnaps(
tsSnaps /*: $ReadOnly<{[string]: string}> */,
tsExtraCases /*: $ReadOnlyArray<string> */,
) {
for (const array2Case of tsExtraCases.filter(
name => name.indexOf('ARRAY2') !== -1,
)) {
const arrayCase = array2Case.replace('ARRAY2', 'ARRAY');
it(`should generate the same snap from fixture ${arrayCase} and ${array2Case}`, () => {
expect(
tsSnaps[
`RN Codegen TypeScript Parser can generate fixture ${arrayCase}`
],
).toEqual(
tsSnaps[
`RN Codegen TypeScript Parser can generate fixture ${array2Case}`
],
);
});
}
}
module.exports = {
compareSnaps,
compareTsArraySnaps,
};

View File

@@ -0,0 +1,375 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {
IncorrectModuleRegistryCallArgumentTypeParserError,
IncorrectModuleRegistryCallArityParserError,
IncorrectModuleRegistryCallTypeParameterParserError,
MisnamedModuleInterfaceParserError,
ModuleInterfaceNotFoundParserError,
MoreThanOneModuleInterfaceParserError,
MoreThanOneModuleRegistryCallsParserError,
UnsupportedArrayElementTypeAnnotationParserError,
UnsupportedFunctionParamTypeAnnotationParserError,
UnsupportedFunctionReturnTypeAnnotationParserError,
UnsupportedModuleEventEmitterPropertyParserError,
UnsupportedModuleEventEmitterTypePropertyParserError,
UnsupportedModulePropertyParserError,
UnsupportedObjectPropertyValueTypeAnnotationParserError,
UntypedModuleRegistryCallParserError,
UnusedModuleInterfaceParserError,
} = require('./errors');
function throwIfModuleInterfaceIsMisnamed(
nativeModuleName,
moduleSpecId,
parserType,
) {
if (moduleSpecId.name !== 'Spec') {
throw new MisnamedModuleInterfaceParserError(
nativeModuleName,
moduleSpecId,
parserType,
);
}
}
function throwIfModuleInterfaceNotFound(
numberOfModuleSpecs,
nativeModuleName,
ast,
parserType,
) {
if (numberOfModuleSpecs === 0) {
throw new ModuleInterfaceNotFoundParserError(
nativeModuleName,
ast,
parserType,
);
}
}
function throwIfMoreThanOneModuleRegistryCalls(
hasteModuleName,
callExpressions,
callExpressionsLength,
) {
if (callExpressions.length > 1) {
throw new MoreThanOneModuleRegistryCallsParserError(
hasteModuleName,
callExpressions,
callExpressionsLength,
);
}
}
function throwIfUnusedModuleInterfaceParserError(
nativeModuleName,
moduleSpec,
callExpressions,
) {
if (callExpressions.length === 0) {
throw new UnusedModuleInterfaceParserError(nativeModuleName, moduleSpec);
}
}
function throwIfWrongNumberOfCallExpressionArgs(
nativeModuleName,
flowCallExpression,
methodName,
numberOfCallExpressionArgs,
) {
if (numberOfCallExpressionArgs !== 1) {
throw new IncorrectModuleRegistryCallArityParserError(
nativeModuleName,
flowCallExpression,
methodName,
numberOfCallExpressionArgs,
);
}
}
function throwIfIncorrectModuleRegistryCallTypeParameterParserError(
nativeModuleName,
typeArguments,
methodName,
moduleName,
parser,
) {
function throwError() {
throw new IncorrectModuleRegistryCallTypeParameterParserError(
nativeModuleName,
typeArguments,
methodName,
moduleName,
);
}
if (parser.checkIfInvalidModule(typeArguments)) {
throwError();
}
}
function throwIfUnsupportedFunctionReturnTypeAnnotationParserError(
nativeModuleName,
returnTypeAnnotation,
invalidReturnType,
cxxOnly,
returnType,
) {
if (!cxxOnly && returnType === 'FunctionTypeAnnotation') {
throw new UnsupportedFunctionReturnTypeAnnotationParserError(
nativeModuleName,
returnTypeAnnotation.returnType,
'FunctionTypeAnnotation',
);
}
}
function throwIfUntypedModule(
typeArguments,
hasteModuleName,
callExpression,
methodName,
moduleName,
) {
if (typeArguments == null) {
throw new UntypedModuleRegistryCallParserError(
hasteModuleName,
callExpression,
methodName,
moduleName,
);
}
}
function throwIfEventEmitterTypeIsUnsupported(
nativeModuleName,
propertyName,
propertyValueType,
parser,
nullable,
untyped,
) {
if (nullable || untyped) {
throw new UnsupportedModuleEventEmitterPropertyParserError(
nativeModuleName,
propertyName,
propertyValueType,
parser.language(),
nullable,
untyped,
);
}
}
function throwIfEventEmitterEventTypeIsUnsupported(
nativeModuleName,
propertyName,
propertyValueType,
parser,
nullable,
) {
if (nullable) {
throw new UnsupportedModuleEventEmitterTypePropertyParserError(
nativeModuleName,
propertyName,
propertyValueType,
parser.language(),
nullable,
);
}
}
function throwIfModuleTypeIsUnsupported(
nativeModuleName,
propertyValue,
propertyName,
propertyValueType,
parser,
) {
if (!parser.functionTypeAnnotation(propertyValueType)) {
throw new UnsupportedModulePropertyParserError(
nativeModuleName,
propertyValue,
propertyName,
propertyValueType,
parser.language(),
);
}
}
const UnsupportedObjectPropertyTypeToInvalidPropertyValueTypeMap = {
FunctionTypeAnnotation: 'FunctionTypeAnnotation',
VoidTypeAnnotation: 'void',
PromiseTypeAnnotation: 'Promise',
};
function throwIfPropertyValueTypeIsUnsupported(
moduleName,
propertyValue,
propertyKey,
type,
) {
const invalidPropertyValueType =
// $FlowFixMe[invalid-computed-prop]
UnsupportedObjectPropertyTypeToInvalidPropertyValueTypeMap[type];
throw new UnsupportedObjectPropertyValueTypeAnnotationParserError(
moduleName,
propertyValue,
propertyKey,
invalidPropertyValueType,
);
}
function throwIfMoreThanOneModuleInterfaceParserError(
nativeModuleName,
moduleSpecs,
parserType,
) {
if (moduleSpecs.length > 1) {
throw new MoreThanOneModuleInterfaceParserError(
nativeModuleName,
moduleSpecs,
moduleSpecs.map(node => node.id.name),
parserType,
);
}
}
function throwIfUnsupportedFunctionParamTypeAnnotationParserError(
nativeModuleName,
languageParamTypeAnnotation,
paramName,
paramTypeAnnotationType,
) {
throw new UnsupportedFunctionParamTypeAnnotationParserError(
nativeModuleName,
languageParamTypeAnnotation,
paramName,
paramTypeAnnotationType,
);
}
function throwIfArrayElementTypeAnnotationIsUnsupported(
hasteModuleName,
flowElementType,
flowArrayType,
type,
) {
const TypeMap = {
FunctionTypeAnnotation: 'FunctionTypeAnnotation',
VoidTypeAnnotation: 'void',
PromiseTypeAnnotation: 'Promise',
// TODO: Added as a work-around for now until TupleTypeAnnotation are fully supported in both flow and TS
// Right now they are partially treated as UnionTypeAnnotation
UnionTypeAnnotation: 'UnionTypeAnnotation',
};
if (type in TypeMap) {
throw new UnsupportedArrayElementTypeAnnotationParserError(
hasteModuleName,
flowElementType,
flowArrayType,
// $FlowFixMe[invalid-computed-prop]
TypeMap[type],
);
}
}
function throwIfIncorrectModuleRegistryCallArgument(
nativeModuleName,
callExpressionArg,
methodName,
) {
if (
callExpressionArg.type !== 'StringLiteral' &&
callExpressionArg.type !== 'Literal'
) {
const {type} = callExpressionArg;
throw new IncorrectModuleRegistryCallArgumentTypeParserError(
nativeModuleName,
callExpressionArg,
methodName,
type,
);
}
}
function throwIfPartialNotAnnotatingTypeParameter(
typeAnnotation,
types,
parser,
) {
const annotatedElement = parser.extractAnnotatedElement(
typeAnnotation,
types,
);
if (!annotatedElement) {
throw new Error('Partials only support annotating a type parameter.');
}
}
function throwIfPartialWithMoreParameter(typeAnnotation) {
if (typeAnnotation.typeParameters.params.length !== 1) {
throw new Error('Partials only support annotating exactly one parameter.');
}
}
function throwIfMoreThanOneCodegenNativecommands(commandsTypeNames) {
if (commandsTypeNames.length > 1) {
throw new Error('codegenNativeCommands may only be called once in a file');
}
}
function throwIfConfigNotfound(foundConfigs) {
if (foundConfigs.length === 0) {
throw new Error('Could not find component config for native component');
}
}
function throwIfMoreThanOneConfig(foundConfigs) {
if (foundConfigs.length > 1) {
throw new Error('Only one component is supported per file');
}
}
function throwIfEventHasNoName(typeAnnotation, parser) {
const name =
parser.language() === 'Flow' ? typeAnnotation.id : typeAnnotation.typeName;
if (!name) {
throw new Error("typeAnnotation of event doesn't have a name");
}
}
function throwIfBubblingTypeIsNull(bubblingType, eventName) {
if (!bubblingType) {
throw new Error(
`Unable to determine event bubbling type for "${eventName}"`,
);
}
return bubblingType;
}
function throwIfArgumentPropsAreNull(argumentProps, eventName) {
if (!argumentProps) {
throw new Error(`Unable to determine event arguments for "${eventName}"`);
}
return argumentProps;
}
function throwIfTypeAliasIsNotInterface(typeAlias, parser) {
if (typeAlias.type !== parser.interfaceDeclaration) {
throw new Error(
`The type argument for codegenNativeCommands must be an interface, received ${typeAlias.type}`,
);
}
}
module.exports = {
throwIfModuleInterfaceIsMisnamed,
throwIfUnsupportedFunctionReturnTypeAnnotationParserError,
throwIfModuleInterfaceNotFound,
throwIfMoreThanOneModuleRegistryCalls,
throwIfPropertyValueTypeIsUnsupported,
throwIfUnusedModuleInterfaceParserError,
throwIfWrongNumberOfCallExpressionArgs,
throwIfIncorrectModuleRegistryCallTypeParameterParserError,
throwIfUntypedModule,
throwIfEventEmitterTypeIsUnsupported,
throwIfEventEmitterEventTypeIsUnsupported,
throwIfModuleTypeIsUnsupported,
throwIfMoreThanOneModuleInterfaceParserError,
throwIfUnsupportedFunctionParamTypeAnnotationParserError,
throwIfArrayElementTypeAnnotationIsUnsupported,
throwIfIncorrectModuleRegistryCallArgument,
throwIfPartialNotAnnotatingTypeParameter,
throwIfPartialWithMoreParameter,
throwIfMoreThanOneCodegenNativecommands,
throwIfConfigNotfound,
throwIfMoreThanOneConfig,
throwIfEventHasNoName,
throwIfBubblingTypeIsNull,
throwIfArgumentPropsAreNull,
throwIfTypeAliasIsNotInterface,
};

View File

@@ -0,0 +1,422 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {NativeModuleTypeAnnotation} from '../CodegenSchema';
import type {TypeDeclarationMap} from '../parsers/utils';
import type {ParserType} from './errors';
import type {Parser} from './parser';
const {
IncorrectModuleRegistryCallArgumentTypeParserError,
IncorrectModuleRegistryCallArityParserError,
IncorrectModuleRegistryCallTypeParameterParserError,
MisnamedModuleInterfaceParserError,
ModuleInterfaceNotFoundParserError,
MoreThanOneModuleInterfaceParserError,
MoreThanOneModuleRegistryCallsParserError,
UnsupportedArrayElementTypeAnnotationParserError,
UnsupportedFunctionParamTypeAnnotationParserError,
UnsupportedFunctionReturnTypeAnnotationParserError,
UnsupportedModuleEventEmitterPropertyParserError,
UnsupportedModuleEventEmitterTypePropertyParserError,
UnsupportedModulePropertyParserError,
UnsupportedObjectPropertyValueTypeAnnotationParserError,
UntypedModuleRegistryCallParserError,
UnusedModuleInterfaceParserError,
} = require('./errors');
function throwIfModuleInterfaceIsMisnamed(
nativeModuleName: string,
moduleSpecId: $FlowFixMe,
parserType: ParserType,
) {
if (moduleSpecId.name !== 'Spec') {
throw new MisnamedModuleInterfaceParserError(
nativeModuleName,
moduleSpecId,
parserType,
);
}
}
function throwIfModuleInterfaceNotFound(
numberOfModuleSpecs: number,
nativeModuleName: string,
ast: $FlowFixMe,
parserType: ParserType,
) {
if (numberOfModuleSpecs === 0) {
throw new ModuleInterfaceNotFoundParserError(
nativeModuleName,
ast,
parserType,
);
}
}
function throwIfMoreThanOneModuleRegistryCalls(
hasteModuleName: string,
callExpressions: $FlowFixMe,
callExpressionsLength: number,
) {
if (callExpressions.length > 1) {
throw new MoreThanOneModuleRegistryCallsParserError(
hasteModuleName,
callExpressions,
callExpressionsLength,
);
}
}
function throwIfUnusedModuleInterfaceParserError(
nativeModuleName: string,
moduleSpec: $FlowFixMe,
callExpressions: $FlowFixMe,
) {
if (callExpressions.length === 0) {
throw new UnusedModuleInterfaceParserError(nativeModuleName, moduleSpec);
}
}
function throwIfWrongNumberOfCallExpressionArgs(
nativeModuleName: string,
flowCallExpression: $FlowFixMe,
methodName: string,
numberOfCallExpressionArgs: number,
) {
if (numberOfCallExpressionArgs !== 1) {
throw new IncorrectModuleRegistryCallArityParserError(
nativeModuleName,
flowCallExpression,
methodName,
numberOfCallExpressionArgs,
);
}
}
function throwIfIncorrectModuleRegistryCallTypeParameterParserError(
nativeModuleName: string,
typeArguments: $FlowFixMe,
methodName: string,
moduleName: string,
parser: Parser,
) {
function throwError() {
throw new IncorrectModuleRegistryCallTypeParameterParserError(
nativeModuleName,
typeArguments,
methodName,
moduleName,
);
}
if (parser.checkIfInvalidModule(typeArguments)) {
throwError();
}
}
function throwIfUnsupportedFunctionReturnTypeAnnotationParserError(
nativeModuleName: string,
returnTypeAnnotation: $FlowFixMe,
invalidReturnType: string,
cxxOnly: boolean,
returnType: string,
) {
if (!cxxOnly && returnType === 'FunctionTypeAnnotation') {
throw new UnsupportedFunctionReturnTypeAnnotationParserError(
nativeModuleName,
returnTypeAnnotation.returnType,
'FunctionTypeAnnotation',
);
}
}
function throwIfUntypedModule(
typeArguments: $FlowFixMe,
hasteModuleName: string,
callExpression: $FlowFixMe,
methodName: string,
moduleName: string,
) {
if (typeArguments == null) {
throw new UntypedModuleRegistryCallParserError(
hasteModuleName,
callExpression,
methodName,
moduleName,
);
}
}
function throwIfEventEmitterTypeIsUnsupported(
nativeModuleName: string,
propertyName: string,
propertyValueType: string,
parser: Parser,
nullable: boolean,
untyped: boolean,
) {
if (nullable || untyped) {
throw new UnsupportedModuleEventEmitterPropertyParserError(
nativeModuleName,
propertyName,
propertyValueType,
parser.language(),
nullable,
untyped,
);
}
}
function throwIfEventEmitterEventTypeIsUnsupported(
nativeModuleName: string,
propertyName: string,
propertyValueType: string,
parser: Parser,
nullable: boolean,
) {
if (nullable) {
throw new UnsupportedModuleEventEmitterTypePropertyParserError(
nativeModuleName,
propertyName,
propertyValueType,
parser.language(),
nullable,
);
}
}
function throwIfModuleTypeIsUnsupported(
nativeModuleName: string,
propertyValue: $FlowFixMe,
propertyName: string,
propertyValueType: string,
parser: Parser,
) {
if (!parser.functionTypeAnnotation(propertyValueType)) {
throw new UnsupportedModulePropertyParserError(
nativeModuleName,
propertyValue,
propertyName,
propertyValueType,
parser.language(),
);
}
}
const UnsupportedObjectPropertyTypeToInvalidPropertyValueTypeMap = {
FunctionTypeAnnotation: 'FunctionTypeAnnotation',
VoidTypeAnnotation: 'void',
PromiseTypeAnnotation: 'Promise',
};
function throwIfPropertyValueTypeIsUnsupported(
moduleName: string,
propertyValue: $FlowFixMe,
propertyKey: string,
type: string,
) {
const invalidPropertyValueType =
// $FlowFixMe[invalid-computed-prop]
UnsupportedObjectPropertyTypeToInvalidPropertyValueTypeMap[type];
throw new UnsupportedObjectPropertyValueTypeAnnotationParserError(
moduleName,
propertyValue,
propertyKey,
invalidPropertyValueType,
);
}
function throwIfMoreThanOneModuleInterfaceParserError(
nativeModuleName: string,
moduleSpecs: $ReadOnlyArray<$FlowFixMe>,
parserType: ParserType,
) {
if (moduleSpecs.length > 1) {
throw new MoreThanOneModuleInterfaceParserError(
nativeModuleName,
moduleSpecs,
moduleSpecs.map(node => node.id.name),
parserType,
);
}
}
function throwIfUnsupportedFunctionParamTypeAnnotationParserError(
nativeModuleName: string,
languageParamTypeAnnotation: $FlowFixMe,
paramName: string,
paramTypeAnnotationType: NativeModuleTypeAnnotation['type'],
) {
throw new UnsupportedFunctionParamTypeAnnotationParserError(
nativeModuleName,
languageParamTypeAnnotation,
paramName,
paramTypeAnnotationType,
);
}
function throwIfArrayElementTypeAnnotationIsUnsupported(
hasteModuleName: string,
flowElementType: $FlowFixMe,
flowArrayType: 'Array' | '$ReadOnlyArray' | 'ReadonlyArray',
type: string,
) {
const TypeMap = {
FunctionTypeAnnotation: 'FunctionTypeAnnotation',
VoidTypeAnnotation: 'void',
PromiseTypeAnnotation: 'Promise',
// TODO: Added as a work-around for now until TupleTypeAnnotation are fully supported in both flow and TS
// Right now they are partially treated as UnionTypeAnnotation
UnionTypeAnnotation: 'UnionTypeAnnotation',
};
if (type in TypeMap) {
throw new UnsupportedArrayElementTypeAnnotationParserError(
hasteModuleName,
flowElementType,
flowArrayType,
// $FlowFixMe[invalid-computed-prop]
TypeMap[type],
);
}
}
function throwIfIncorrectModuleRegistryCallArgument(
nativeModuleName: string,
callExpressionArg: $FlowFixMe,
methodName: string,
) {
if (
callExpressionArg.type !== 'StringLiteral' &&
callExpressionArg.type !== 'Literal'
) {
const {type} = callExpressionArg;
throw new IncorrectModuleRegistryCallArgumentTypeParserError(
nativeModuleName,
callExpressionArg,
methodName,
type,
);
}
}
function throwIfPartialNotAnnotatingTypeParameter(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
parser: Parser,
) {
const annotatedElement = parser.extractAnnotatedElement(
typeAnnotation,
types,
);
if (!annotatedElement) {
throw new Error('Partials only support annotating a type parameter.');
}
}
function throwIfPartialWithMoreParameter(typeAnnotation: $FlowFixMe) {
if (typeAnnotation.typeParameters.params.length !== 1) {
throw new Error('Partials only support annotating exactly one parameter.');
}
}
function throwIfMoreThanOneCodegenNativecommands(
commandsTypeNames: $ReadOnlyArray<$FlowFixMe>,
) {
if (commandsTypeNames.length > 1) {
throw new Error('codegenNativeCommands may only be called once in a file');
}
}
function throwIfConfigNotfound(foundConfigs: Array<{[string]: string}>) {
if (foundConfigs.length === 0) {
throw new Error('Could not find component config for native component');
}
}
function throwIfMoreThanOneConfig(foundConfigs: Array<{[string]: string}>) {
if (foundConfigs.length > 1) {
throw new Error('Only one component is supported per file');
}
}
function throwIfEventHasNoName(typeAnnotation: $FlowFixMe, parser: Parser) {
const name =
parser.language() === 'Flow' ? typeAnnotation.id : typeAnnotation.typeName;
if (!name) {
throw new Error("typeAnnotation of event doesn't have a name");
}
}
function throwIfBubblingTypeIsNull(
bubblingType: ?('direct' | 'bubble'),
eventName: string,
): 'direct' | 'bubble' {
if (!bubblingType) {
throw new Error(
`Unable to determine event bubbling type for "${eventName}"`,
);
}
return bubblingType;
}
function throwIfArgumentPropsAreNull(
argumentProps: ?$ReadOnlyArray<$FlowFixMe>,
eventName: string,
): $ReadOnlyArray<$FlowFixMe> {
if (!argumentProps) {
throw new Error(`Unable to determine event arguments for "${eventName}"`);
}
return argumentProps;
}
function throwIfTypeAliasIsNotInterface(typeAlias: $FlowFixMe, parser: Parser) {
if (typeAlias.type !== parser.interfaceDeclaration) {
throw new Error(
`The type argument for codegenNativeCommands must be an interface, received ${typeAlias.type}`,
);
}
}
module.exports = {
throwIfModuleInterfaceIsMisnamed,
throwIfUnsupportedFunctionReturnTypeAnnotationParserError,
throwIfModuleInterfaceNotFound,
throwIfMoreThanOneModuleRegistryCalls,
throwIfPropertyValueTypeIsUnsupported,
throwIfUnusedModuleInterfaceParserError,
throwIfWrongNumberOfCallExpressionArgs,
throwIfIncorrectModuleRegistryCallTypeParameterParserError,
throwIfUntypedModule,
throwIfEventEmitterTypeIsUnsupported,
throwIfEventEmitterEventTypeIsUnsupported,
throwIfModuleTypeIsUnsupported,
throwIfMoreThanOneModuleInterfaceParserError,
throwIfUnsupportedFunctionParamTypeAnnotationParserError,
throwIfArrayElementTypeAnnotationIsUnsupported,
throwIfIncorrectModuleRegistryCallArgument,
throwIfPartialNotAnnotatingTypeParameter,
throwIfPartialWithMoreParameter,
throwIfMoreThanOneCodegenNativecommands,
throwIfConfigNotfound,
throwIfMoreThanOneConfig,
throwIfEventHasNoName,
throwIfBubblingTypeIsNull,
throwIfArgumentPropsAreNull,
throwIfTypeAliasIsNotInterface,
};

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export type ParserType = 'Flow' | 'TypeScript';
export declare class ParserError extends Error {}

View File

@@ -0,0 +1,374 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
class ParserError extends Error {
constructor(nativeModuleName, astNodeOrNodes, message) {
super(`Module ${nativeModuleName}: ${message}`);
this.nodes = Array.isArray(astNodeOrNodes)
? astNodeOrNodes
: [astNodeOrNodes];
// assign the error class name in your custom error (as a shortcut)
this.name = this.constructor.name;
// capturing the stack trace keeps the reference to your error class
Error.captureStackTrace(this, this.constructor);
}
}
class MisnamedModuleInterfaceParserError extends ParserError {
constructor(nativeModuleName, id, language) {
super(
nativeModuleName,
id,
`All ${language} interfaces extending TurboModule must be called 'Spec'. Please rename ${language} interface '${id.name}' to 'Spec'.`,
);
}
}
class ModuleInterfaceNotFoundParserError extends ParserError {
constructor(nativeModuleName, ast, language) {
super(
nativeModuleName,
ast,
`No ${language} interfaces extending TurboModule were detected in this NativeModule spec.`,
);
}
}
class MoreThanOneModuleInterfaceParserError extends ParserError {
constructor(nativeModuleName, flowModuleInterfaces, names, language) {
const finalName = names[names.length - 1];
const allButLastName = names.slice(0, -1);
const quote = x => `'${x}'`;
const nameStr =
allButLastName.map(quote).join(', ') + ', and ' + quote(finalName);
super(
nativeModuleName,
flowModuleInterfaces,
`Every NativeModule spec file must declare exactly one NativeModule ${language} interface. This file declares ${names.length}: ${nameStr}. Please remove the extraneous ${language} interface declarations.`,
);
}
}
class UnsupportedModuleEventEmitterTypePropertyParserError extends ParserError {
constructor(
nativeModuleName,
propertyValue,
propertyName,
language,
nullable,
) {
super(
nativeModuleName,
propertyValue,
`Property '${propertyName}' is an EventEmitter and must have a non nullable eventType`,
);
}
}
class UnsupportedModuleEventEmitterPropertyParserError extends ParserError {
constructor(
nativeModuleName,
propertyValue,
propertyName,
language,
nullable,
untyped,
) {
let message = `${language} interfaces extending TurboModule must only contain 'FunctionTypeAnnotation's or non nullable 'EventEmitter's. Further the EventEmitter property `;
if (nullable) {
message += `'${propertyValue}' must non nullable.`;
} else if (untyped) {
message += `'${propertyValue}' must have a concrete or void eventType.`;
}
super(nativeModuleName, propertyValue, message);
}
}
class UnsupportedModulePropertyParserError extends ParserError {
constructor(
nativeModuleName,
propertyValue,
propertyName,
invalidPropertyValueType,
language,
) {
super(
nativeModuleName,
propertyValue,
`${language} interfaces extending TurboModule must only contain 'FunctionTypeAnnotation's or non nullable 'EventEmitter's. Property '${propertyName}' refers to a '${invalidPropertyValueType}'.`,
);
}
}
class UnsupportedTypeAnnotationParserError extends ParserError {
constructor(nativeModuleName, typeAnnotation, language) {
super(
nativeModuleName,
typeAnnotation,
`${language} type annotation '${typeAnnotation.type}' is unsupported in NativeModule specs.`,
);
this.typeAnnotationType = typeAnnotation.type;
}
}
class UnsupportedGenericParserError extends ParserError {
// +genericName: string;
constructor(nativeModuleName, genericTypeAnnotation, parser) {
const genericName = parser.getTypeAnnotationName(genericTypeAnnotation);
super(
nativeModuleName,
genericTypeAnnotation,
`Unrecognized generic type '${genericName}' in NativeModule spec.`,
);
// this.genericName = genericName;
}
}
class MissingTypeParameterGenericParserError extends ParserError {
constructor(nativeModuleName, genericTypeAnnotation, parser) {
const genericName = parser.getTypeAnnotationName(genericTypeAnnotation);
super(
nativeModuleName,
genericTypeAnnotation,
`Generic '${genericName}' must have type parameters.`,
);
}
}
class MoreThanOneTypeParameterGenericParserError extends ParserError {
constructor(nativeModuleName, genericTypeAnnotation, parser) {
const genericName = parser.getTypeAnnotationName(genericTypeAnnotation);
super(
nativeModuleName,
genericTypeAnnotation,
`Generic '${genericName}' must have exactly one type parameter.`,
);
}
}
/**
* Array parsing errors
*/
class UnsupportedArrayElementTypeAnnotationParserError extends ParserError {
constructor(
nativeModuleName,
arrayElementTypeAST,
arrayType,
invalidArrayElementType,
) {
super(
nativeModuleName,
arrayElementTypeAST,
`${arrayType} element types cannot be '${invalidArrayElementType}'.`,
);
}
}
/**
* Object parsing errors
*/
class UnsupportedObjectPropertyTypeAnnotationParserError extends ParserError {
constructor(nativeModuleName, propertyAST, invalidPropertyType, language) {
let message = `'ObjectTypeAnnotation' cannot contain '${invalidPropertyType}'.`;
if (
invalidPropertyType === 'ObjectTypeSpreadProperty' &&
language !== 'TypeScript'
) {
message = "Object spread isn't supported in 'ObjectTypeAnnotation's.";
}
super(nativeModuleName, propertyAST, message);
}
}
class UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError extends ParserError {
constructor(nativeModuleName, propertyAST) {
let message =
"'ObjectTypeAnnotation' cannot contain both an indexer and properties.";
super(nativeModuleName, propertyAST, message);
}
}
class UnsupportedObjectPropertyValueTypeAnnotationParserError extends ParserError {
constructor(
nativeModuleName,
propertyValueAST,
propertyName,
invalidPropertyValueType,
) {
super(
nativeModuleName,
propertyValueAST,
`Object property '${propertyName}' cannot have type '${invalidPropertyValueType}'.`,
);
}
}
class UnsupportedObjectDirectRecursivePropertyParserError extends ParserError {
constructor(propertyName, propertyValueAST, nativeModuleName) {
super(
nativeModuleName,
propertyValueAST,
`Object property '${propertyName}' is direct recursive and must be nullable.`,
);
}
}
/**
* Function parsing errors
*/
class UnnamedFunctionParamParserError extends ParserError {
constructor(functionParam, nativeModuleName) {
super(
nativeModuleName,
functionParam,
'All function parameters must be named.',
);
}
}
class UnsupportedFunctionParamTypeAnnotationParserError extends ParserError {
constructor(
nativeModuleName,
flowParamTypeAnnotation,
paramName,
invalidParamType,
) {
super(
nativeModuleName,
flowParamTypeAnnotation,
`Function parameter '${paramName}' cannot have type '${invalidParamType}'.`,
);
}
}
class UnsupportedFunctionReturnTypeAnnotationParserError extends ParserError {
constructor(nativeModuleName, flowReturnTypeAnnotation, invalidReturnType) {
super(
nativeModuleName,
flowReturnTypeAnnotation,
`Function return cannot have type '${invalidReturnType}'.`,
);
}
}
/**
* Enum parsing errors
*/
class UnsupportedEnumDeclarationParserError extends ParserError {
constructor(nativeModuleName, arrayElementTypeAST, memberType) {
super(
nativeModuleName,
arrayElementTypeAST,
`Unexpected enum member type ${memberType}. Only string and number enum members are supported`,
);
}
}
/**
* Union parsing errors
*/
class UnsupportedUnionTypeAnnotationParserError extends ParserError {
constructor(nativeModuleName, arrayElementTypeAST, types) {
super(
nativeModuleName,
arrayElementTypeAST,
`Union members must be of the same type, but multiple types were found ${types.join(', ')}'.`,
);
}
}
/**
* Module parsing errors
*/
class UnusedModuleInterfaceParserError extends ParserError {
constructor(nativeModuleName, flowInterface) {
super(
nativeModuleName,
flowInterface,
"Unused NativeModule spec. Please load the NativeModule by calling TurboModuleRegistry.get<Spec>('<moduleName>').",
);
}
}
class MoreThanOneModuleRegistryCallsParserError extends ParserError {
constructor(nativeModuleName, flowCallExpressions, numCalls) {
super(
nativeModuleName,
flowCallExpressions,
`Every NativeModule spec file must contain exactly one NativeModule load. This file contains ${numCalls}. Please simplify this spec file, splitting it as necessary, to remove the extraneous loads.`,
);
}
}
class UntypedModuleRegistryCallParserError extends ParserError {
constructor(nativeModuleName, flowCallExpression, methodName, moduleName) {
super(
nativeModuleName,
flowCallExpression,
`Please type this NativeModule load: TurboModuleRegistry.${methodName}<Spec>('${moduleName}').`,
);
}
}
class IncorrectModuleRegistryCallTypeParameterParserError extends ParserError {
constructor(nativeModuleName, flowTypeArguments, methodName, moduleName) {
super(
nativeModuleName,
flowTypeArguments,
`Please change these type arguments to reflect TurboModuleRegistry.${methodName}<Spec>('${moduleName}').`,
);
}
}
class IncorrectModuleRegistryCallArityParserError extends ParserError {
constructor(
nativeModuleName,
flowCallExpression,
methodName,
incorrectArity,
) {
super(
nativeModuleName,
flowCallExpression,
`Please call TurboModuleRegistry.${methodName}<Spec>() with exactly one argument. Detected ${incorrectArity}.`,
);
}
}
class IncorrectModuleRegistryCallArgumentTypeParserError extends ParserError {
constructor(nativeModuleName, flowArgument, methodName, type) {
const a = /[aeiouy]/.test(type.toLowerCase()) ? 'an' : 'a';
super(
nativeModuleName,
flowArgument,
`Please call TurboModuleRegistry.${methodName}<Spec>() with a string literal. Detected ${a} '${type}'`,
);
}
}
module.exports = {
ParserError,
MissingTypeParameterGenericParserError,
MoreThanOneTypeParameterGenericParserError,
MisnamedModuleInterfaceParserError,
ModuleInterfaceNotFoundParserError,
MoreThanOneModuleInterfaceParserError,
UnnamedFunctionParamParserError,
UnsupportedArrayElementTypeAnnotationParserError,
UnsupportedGenericParserError,
UnsupportedTypeAnnotationParserError,
UnsupportedFunctionParamTypeAnnotationParserError,
UnsupportedFunctionReturnTypeAnnotationParserError,
UnsupportedEnumDeclarationParserError,
UnsupportedUnionTypeAnnotationParserError,
UnsupportedModuleEventEmitterTypePropertyParserError,
UnsupportedModuleEventEmitterPropertyParserError,
UnsupportedModulePropertyParserError,
UnsupportedObjectPropertyTypeAnnotationParserError,
UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError,
UnsupportedObjectPropertyValueTypeAnnotationParserError,
UnsupportedObjectDirectRecursivePropertyParserError,
UnusedModuleInterfaceParserError,
MoreThanOneModuleRegistryCallsParserError,
UntypedModuleRegistryCallParserError,
IncorrectModuleRegistryCallTypeParameterParserError,
IncorrectModuleRegistryCallArityParserError,
IncorrectModuleRegistryCallArgumentTypeParserError,
};

View File

@@ -0,0 +1,477 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {UnionTypeAnnotationMemberType} from '../CodegenSchema';
import type {Parser} from './parser';
export type ParserType = 'Flow' | 'TypeScript';
class ParserError extends Error {
nodes: $ReadOnlyArray<$FlowFixMe>;
constructor(
nativeModuleName: string,
astNodeOrNodes: $FlowFixMe,
message: string,
) {
super(`Module ${nativeModuleName}: ${message}`);
this.nodes = Array.isArray(astNodeOrNodes)
? astNodeOrNodes
: [astNodeOrNodes];
// assign the error class name in your custom error (as a shortcut)
this.name = this.constructor.name;
// capturing the stack trace keeps the reference to your error class
Error.captureStackTrace(this, this.constructor);
}
}
class MisnamedModuleInterfaceParserError extends ParserError {
constructor(nativeModuleName: string, id: $FlowFixMe, language: ParserType) {
super(
nativeModuleName,
id,
`All ${language} interfaces extending TurboModule must be called 'Spec'. Please rename ${language} interface '${id.name}' to 'Spec'.`,
);
}
}
class ModuleInterfaceNotFoundParserError extends ParserError {
constructor(nativeModuleName: string, ast: $FlowFixMe, language: ParserType) {
super(
nativeModuleName,
ast,
`No ${language} interfaces extending TurboModule were detected in this NativeModule spec.`,
);
}
}
class MoreThanOneModuleInterfaceParserError extends ParserError {
constructor(
nativeModuleName: string,
flowModuleInterfaces: $ReadOnlyArray<$FlowFixMe>,
names: $ReadOnlyArray<string>,
language: ParserType,
) {
const finalName = names[names.length - 1];
const allButLastName = names.slice(0, -1);
const quote = (x: string) => `'${x}'`;
const nameStr =
allButLastName.map(quote).join(', ') + ', and ' + quote(finalName);
super(
nativeModuleName,
flowModuleInterfaces,
`Every NativeModule spec file must declare exactly one NativeModule ${language} interface. This file declares ${names.length}: ${nameStr}. Please remove the extraneous ${language} interface declarations.`,
);
}
}
class UnsupportedModuleEventEmitterTypePropertyParserError extends ParserError {
constructor(
nativeModuleName: string,
propertyValue: $FlowFixMe,
propertyName: string,
language: ParserType,
nullable: boolean,
) {
super(
nativeModuleName,
propertyValue,
`Property '${propertyName}' is an EventEmitter and must have a non nullable eventType`,
);
}
}
class UnsupportedModuleEventEmitterPropertyParserError extends ParserError {
constructor(
nativeModuleName: string,
propertyValue: $FlowFixMe,
propertyName: string,
language: ParserType,
nullable: boolean,
untyped: boolean,
) {
let message = `${language} interfaces extending TurboModule must only contain 'FunctionTypeAnnotation's or non nullable 'EventEmitter's. Further the EventEmitter property `;
if (nullable) {
message += `'${propertyValue}' must non nullable.`;
} else if (untyped) {
message += `'${propertyValue}' must have a concrete or void eventType.`;
}
super(nativeModuleName, propertyValue, message);
}
}
class UnsupportedModulePropertyParserError extends ParserError {
constructor(
nativeModuleName: string,
propertyValue: $FlowFixMe,
propertyName: string,
invalidPropertyValueType: string,
language: ParserType,
) {
super(
nativeModuleName,
propertyValue,
`${language} interfaces extending TurboModule must only contain 'FunctionTypeAnnotation's or non nullable 'EventEmitter's. Property '${propertyName}' refers to a '${invalidPropertyValueType}'.`,
);
}
}
class UnsupportedTypeAnnotationParserError extends ParserError {
+typeAnnotationType: string;
constructor(
nativeModuleName: string,
typeAnnotation: $FlowFixMe,
language: ParserType,
) {
super(
nativeModuleName,
typeAnnotation,
`${language} type annotation '${typeAnnotation.type}' is unsupported in NativeModule specs.`,
);
this.typeAnnotationType = typeAnnotation.type;
}
}
class UnsupportedGenericParserError extends ParserError {
// +genericName: string;
constructor(
nativeModuleName: string,
genericTypeAnnotation: $FlowFixMe,
parser: Parser,
) {
const genericName = parser.getTypeAnnotationName(genericTypeAnnotation);
super(
nativeModuleName,
genericTypeAnnotation,
`Unrecognized generic type '${genericName}' in NativeModule spec.`,
);
// this.genericName = genericName;
}
}
class MissingTypeParameterGenericParserError extends ParserError {
constructor(
nativeModuleName: string,
genericTypeAnnotation: $FlowFixMe,
parser: Parser,
) {
const genericName = parser.getTypeAnnotationName(genericTypeAnnotation);
super(
nativeModuleName,
genericTypeAnnotation,
`Generic '${genericName}' must have type parameters.`,
);
}
}
class MoreThanOneTypeParameterGenericParserError extends ParserError {
constructor(
nativeModuleName: string,
genericTypeAnnotation: $FlowFixMe,
parser: Parser,
) {
const genericName = parser.getTypeAnnotationName(genericTypeAnnotation);
super(
nativeModuleName,
genericTypeAnnotation,
`Generic '${genericName}' must have exactly one type parameter.`,
);
}
}
/**
* Array parsing errors
*/
class UnsupportedArrayElementTypeAnnotationParserError extends ParserError {
constructor(
nativeModuleName: string,
arrayElementTypeAST: $FlowFixMe,
arrayType: 'Array' | '$ReadOnlyArray' | 'ReadonlyArray',
invalidArrayElementType: string,
) {
super(
nativeModuleName,
arrayElementTypeAST,
`${arrayType} element types cannot be '${invalidArrayElementType}'.`,
);
}
}
/**
* Object parsing errors
*/
class UnsupportedObjectPropertyTypeAnnotationParserError extends ParserError {
constructor(
nativeModuleName: string,
propertyAST: $FlowFixMe,
invalidPropertyType: string,
language: ParserType,
) {
let message = `'ObjectTypeAnnotation' cannot contain '${invalidPropertyType}'.`;
if (
invalidPropertyType === 'ObjectTypeSpreadProperty' &&
language !== 'TypeScript'
) {
message = "Object spread isn't supported in 'ObjectTypeAnnotation's.";
}
super(nativeModuleName, propertyAST, message);
}
}
class UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError extends ParserError {
constructor(nativeModuleName: string, propertyAST: $FlowFixMe) {
let message =
"'ObjectTypeAnnotation' cannot contain both an indexer and properties.";
super(nativeModuleName, propertyAST, message);
}
}
class UnsupportedObjectPropertyValueTypeAnnotationParserError extends ParserError {
constructor(
nativeModuleName: string,
propertyValueAST: $FlowFixMe,
propertyName: string,
invalidPropertyValueType: string,
) {
super(
nativeModuleName,
propertyValueAST,
`Object property '${propertyName}' cannot have type '${invalidPropertyValueType}'.`,
);
}
}
class UnsupportedObjectDirectRecursivePropertyParserError extends ParserError {
constructor(
propertyName: string,
propertyValueAST: $FlowFixMe,
nativeModuleName: string,
) {
super(
nativeModuleName,
propertyValueAST,
`Object property '${propertyName}' is direct recursive and must be nullable.`,
);
}
}
/**
* Function parsing errors
*/
class UnnamedFunctionParamParserError extends ParserError {
constructor(functionParam: $FlowFixMe, nativeModuleName: string) {
super(
nativeModuleName,
functionParam,
'All function parameters must be named.',
);
}
}
class UnsupportedFunctionParamTypeAnnotationParserError extends ParserError {
constructor(
nativeModuleName: string,
flowParamTypeAnnotation: $FlowFixMe,
paramName: string,
invalidParamType: string,
) {
super(
nativeModuleName,
flowParamTypeAnnotation,
`Function parameter '${paramName}' cannot have type '${invalidParamType}'.`,
);
}
}
class UnsupportedFunctionReturnTypeAnnotationParserError extends ParserError {
constructor(
nativeModuleName: string,
flowReturnTypeAnnotation: $FlowFixMe,
invalidReturnType: string,
) {
super(
nativeModuleName,
flowReturnTypeAnnotation,
`Function return cannot have type '${invalidReturnType}'.`,
);
}
}
/**
* Enum parsing errors
*/
class UnsupportedEnumDeclarationParserError extends ParserError {
constructor(
nativeModuleName: string,
arrayElementTypeAST: $FlowFixMe,
memberType: string,
) {
super(
nativeModuleName,
arrayElementTypeAST,
`Unexpected enum member type ${memberType}. Only string and number enum members are supported`,
);
}
}
/**
* Union parsing errors
*/
class UnsupportedUnionTypeAnnotationParserError extends ParserError {
constructor(
nativeModuleName: string,
arrayElementTypeAST: $FlowFixMe,
types: UnionTypeAnnotationMemberType[],
) {
super(
nativeModuleName,
arrayElementTypeAST,
`Union members must be of the same type, but multiple types were found ${types.join(
', ',
)}'.`,
);
}
}
/**
* Module parsing errors
*/
class UnusedModuleInterfaceParserError extends ParserError {
constructor(nativeModuleName: string, flowInterface: $FlowFixMe) {
super(
nativeModuleName,
flowInterface,
"Unused NativeModule spec. Please load the NativeModule by calling TurboModuleRegistry.get<Spec>('<moduleName>').",
);
}
}
class MoreThanOneModuleRegistryCallsParserError extends ParserError {
constructor(
nativeModuleName: string,
flowCallExpressions: $FlowFixMe,
numCalls: number,
) {
super(
nativeModuleName,
flowCallExpressions,
`Every NativeModule spec file must contain exactly one NativeModule load. This file contains ${numCalls}. Please simplify this spec file, splitting it as necessary, to remove the extraneous loads.`,
);
}
}
class UntypedModuleRegistryCallParserError extends ParserError {
constructor(
nativeModuleName: string,
flowCallExpression: $FlowFixMe,
methodName: string,
moduleName: string,
) {
super(
nativeModuleName,
flowCallExpression,
`Please type this NativeModule load: TurboModuleRegistry.${methodName}<Spec>('${moduleName}').`,
);
}
}
class IncorrectModuleRegistryCallTypeParameterParserError extends ParserError {
constructor(
nativeModuleName: string,
flowTypeArguments: $FlowFixMe,
methodName: string,
moduleName: string,
) {
super(
nativeModuleName,
flowTypeArguments,
`Please change these type arguments to reflect TurboModuleRegistry.${methodName}<Spec>('${moduleName}').`,
);
}
}
class IncorrectModuleRegistryCallArityParserError extends ParserError {
constructor(
nativeModuleName: string,
flowCallExpression: $FlowFixMe,
methodName: string,
incorrectArity: number,
) {
super(
nativeModuleName,
flowCallExpression,
`Please call TurboModuleRegistry.${methodName}<Spec>() with exactly one argument. Detected ${incorrectArity}.`,
);
}
}
class IncorrectModuleRegistryCallArgumentTypeParserError extends ParserError {
constructor(
nativeModuleName: string,
flowArgument: $FlowFixMe,
methodName: string,
type: string,
) {
const a = /[aeiouy]/.test(type.toLowerCase()) ? 'an' : 'a';
super(
nativeModuleName,
flowArgument,
`Please call TurboModuleRegistry.${methodName}<Spec>() with a string literal. Detected ${a} '${type}'`,
);
}
}
module.exports = {
ParserError,
MissingTypeParameterGenericParserError,
MoreThanOneTypeParameterGenericParserError,
MisnamedModuleInterfaceParserError,
ModuleInterfaceNotFoundParserError,
MoreThanOneModuleInterfaceParserError,
UnnamedFunctionParamParserError,
UnsupportedArrayElementTypeAnnotationParserError,
UnsupportedGenericParserError,
UnsupportedTypeAnnotationParserError,
UnsupportedFunctionParamTypeAnnotationParserError,
UnsupportedFunctionReturnTypeAnnotationParserError,
UnsupportedEnumDeclarationParserError,
UnsupportedUnionTypeAnnotationParserError,
UnsupportedModuleEventEmitterTypePropertyParserError,
UnsupportedModuleEventEmitterPropertyParserError,
UnsupportedModulePropertyParserError,
UnsupportedObjectPropertyTypeAnnotationParserError,
UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError,
UnsupportedObjectPropertyValueTypeAnnotationParserError,
UnsupportedObjectDirectRecursivePropertyParserError,
UnusedModuleInterfaceParserError,
MoreThanOneModuleRegistryCallsParserError,
UntypedModuleRegistryCallParserError,
IncorrectModuleRegistryCallTypeParameterParserError,
IncorrectModuleRegistryCallArityParserError,
IncorrectModuleRegistryCallArgumentTypeParserError,
};

View File

@@ -0,0 +1,187 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {getValueFromTypes} = require('../utils.js');
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
function buildCommandSchema(property, types, parser) {
const name = property.key.name;
const optional = property.optional;
const value = getValueFromTypes(property.value, types);
const firstParam = value.params[0].typeAnnotation;
if (
!(
firstParam.id != null &&
firstParam.id.type === 'QualifiedTypeIdentifier' &&
firstParam.id.qualification.name === 'React' &&
firstParam.id.id.name === 'ElementRef'
)
) {
throw new Error(
`The first argument of method ${name} must be of type React.ElementRef<>`,
);
}
const params = value.params.slice(1).map(param => {
const paramName = param.name.name;
const paramValue = getValueFromTypes(param.typeAnnotation, types);
const type =
paramValue.type === 'GenericTypeAnnotation'
? parser.getTypeAnnotationName(paramValue)
: paramValue.type;
let returnType;
switch (type) {
case 'RootTag':
returnType = {
type: 'ReservedTypeAnnotation',
name: 'RootTag',
};
break;
case 'BooleanTypeAnnotation':
returnType = {
type: 'BooleanTypeAnnotation',
};
break;
case 'Int32':
returnType = {
type: 'Int32TypeAnnotation',
};
break;
case 'Double':
returnType = {
type: 'DoubleTypeAnnotation',
};
break;
case 'Float':
returnType = {
type: 'FloatTypeAnnotation',
};
break;
case 'StringTypeAnnotation':
returnType = {
type: 'StringTypeAnnotation',
};
break;
case 'Array':
case '$ReadOnlyArray':
/* $FlowFixMe[invalid-compare] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/4oq3zi07. */
if (!paramValue.type === 'GenericTypeAnnotation') {
throw new Error(
'Array and $ReadOnlyArray are GenericTypeAnnotation for array',
);
}
returnType = {
type: 'ArrayTypeAnnotation',
elementType: getCommandArrayElementTypeType(
paramValue.typeParameters.params[0],
parser,
),
};
break;
case 'ArrayTypeAnnotation':
returnType = {
type: 'ArrayTypeAnnotation',
elementType: getCommandArrayElementTypeType(
paramValue.elementType,
parser,
),
};
break;
default:
type;
throw new Error(
`Unsupported param type for method "${name}", param "${paramName}". Found ${type}`,
);
}
return {
name: paramName,
optional: false,
typeAnnotation: returnType,
};
});
return {
name,
optional,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
params,
returnTypeAnnotation: {
type: 'VoidTypeAnnotation',
},
},
};
}
function getCommandArrayElementTypeType(inputType, parser) {
// TODO: T172453752 support more complex type annotation for array element
if (typeof inputType !== 'object') {
throw new Error('Expected an object');
}
const type =
inputType === null || inputType === void 0 ? void 0 : inputType.type;
if (inputType == null || typeof type !== 'string') {
throw new Error('Command array element type must be a string');
}
switch (type) {
case 'BooleanTypeAnnotation':
return {
type: 'BooleanTypeAnnotation',
};
case 'StringTypeAnnotation':
return {
type: 'StringTypeAnnotation',
};
case 'GenericTypeAnnotation':
const name =
typeof inputType.id === 'object'
? parser.getTypeAnnotationName(inputType)
: null;
if (typeof name !== 'string') {
throw new Error(
'Expected GenericTypeAnnotation AST name to be a string',
);
}
switch (name) {
case 'Int32':
return {
type: 'Int32TypeAnnotation',
};
case 'Float':
return {
type: 'FloatTypeAnnotation',
};
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
default:
// This is not a great solution. This generally means its a type alias to another type
// like an object or union. Ideally we'd encode that in the schema so the compat-check can
// validate those deeper objects for breaking changes and the generators can do something smarter.
// As of now, the generators just create ReadableMap or (const NSArray *) which are untyped
return {
type: 'MixedTypeAnnotation',
};
}
default:
throw new Error(`Unsupported array element type ${type}`);
}
}
function getCommands(commandTypeAST, types, parser) {
return commandTypeAST
.filter(property => property.type === 'ObjectTypeProperty')
.map(property => buildCommandSchema(property, types, parser))
.filter(Boolean);
}
module.exports = {
getCommands,
};

View File

@@ -0,0 +1,238 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
CommandParamTypeAnnotation,
CommandTypeAnnotation,
ComponentCommandArrayTypeAnnotation,
NamedShape,
} from '../../../CodegenSchema.js';
import type {Parser} from '../../parser';
import type {TypeDeclarationMap} from '../../utils';
const {getValueFromTypes} = require('../utils.js');
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
type EventTypeAST = Object;
function buildCommandSchema(
property: EventTypeAST,
types: TypeDeclarationMap,
parser: Parser,
): $ReadOnly<{
name: string,
optional: boolean,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
params: $ReadOnlyArray<{
name: string,
optional: boolean,
typeAnnotation: CommandParamTypeAnnotation,
}>,
returnTypeAnnotation: {
type: 'VoidTypeAnnotation',
},
},
}> {
const name = property.key.name;
const optional = property.optional;
const value = getValueFromTypes(property.value, types);
const firstParam = value.params[0].typeAnnotation;
if (
!(
firstParam.id != null &&
firstParam.id.type === 'QualifiedTypeIdentifier' &&
firstParam.id.qualification.name === 'React' &&
firstParam.id.id.name === 'ElementRef'
)
) {
throw new Error(
`The first argument of method ${name} must be of type React.ElementRef<>`,
);
}
const params = value.params.slice(1).map(param => {
const paramName = param.name.name;
const paramValue = getValueFromTypes(param.typeAnnotation, types);
const type =
paramValue.type === 'GenericTypeAnnotation'
? parser.getTypeAnnotationName(paramValue)
: paramValue.type;
let returnType: CommandParamTypeAnnotation;
switch (type) {
case 'RootTag':
returnType = {
type: 'ReservedTypeAnnotation',
name: 'RootTag',
};
break;
case 'BooleanTypeAnnotation':
returnType = {
type: 'BooleanTypeAnnotation',
};
break;
case 'Int32':
returnType = {
type: 'Int32TypeAnnotation',
};
break;
case 'Double':
returnType = {
type: 'DoubleTypeAnnotation',
};
break;
case 'Float':
returnType = {
type: 'FloatTypeAnnotation',
};
break;
case 'StringTypeAnnotation':
returnType = {
type: 'StringTypeAnnotation',
};
break;
case 'Array':
case '$ReadOnlyArray':
/* $FlowFixMe[invalid-compare] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/4oq3zi07. */
if (!paramValue.type === 'GenericTypeAnnotation') {
throw new Error(
'Array and $ReadOnlyArray are GenericTypeAnnotation for array',
);
}
returnType = {
type: 'ArrayTypeAnnotation',
elementType: getCommandArrayElementTypeType(
paramValue.typeParameters.params[0],
parser,
),
};
break;
case 'ArrayTypeAnnotation':
returnType = {
type: 'ArrayTypeAnnotation',
elementType: getCommandArrayElementTypeType(
paramValue.elementType,
parser,
),
};
break;
default:
(type: mixed);
throw new Error(
`Unsupported param type for method "${name}", param "${paramName}". Found ${type}`,
);
}
return {
name: paramName,
optional: false,
typeAnnotation: returnType,
};
});
return {
name,
optional,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
params,
returnTypeAnnotation: {
type: 'VoidTypeAnnotation',
},
},
};
}
type Allowed = ComponentCommandArrayTypeAnnotation['elementType'];
function getCommandArrayElementTypeType(
inputType: mixed,
parser: Parser,
): Allowed {
// TODO: T172453752 support more complex type annotation for array element
if (typeof inputType !== 'object') {
throw new Error('Expected an object');
}
const type = inputType?.type;
if (inputType == null || typeof type !== 'string') {
throw new Error('Command array element type must be a string');
}
switch (type) {
case 'BooleanTypeAnnotation':
return {
type: 'BooleanTypeAnnotation',
};
case 'StringTypeAnnotation':
return {
type: 'StringTypeAnnotation',
};
case 'GenericTypeAnnotation':
const name =
typeof inputType.id === 'object'
? parser.getTypeAnnotationName(inputType)
: null;
if (typeof name !== 'string') {
throw new Error(
'Expected GenericTypeAnnotation AST name to be a string',
);
}
switch (name) {
case 'Int32':
return {
type: 'Int32TypeAnnotation',
};
case 'Float':
return {
type: 'FloatTypeAnnotation',
};
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
default:
// This is not a great solution. This generally means its a type alias to another type
// like an object or union. Ideally we'd encode that in the schema so the compat-check can
// validate those deeper objects for breaking changes and the generators can do something smarter.
// As of now, the generators just create ReadableMap or (const NSArray *) which are untyped
return {
type: 'MixedTypeAnnotation',
};
}
default:
throw new Error(`Unsupported array element type ${type}`);
}
}
function getCommands(
commandTypeAST: $ReadOnlyArray<EventTypeAST>,
types: TypeDeclarationMap,
parser: Parser,
): $ReadOnlyArray<NamedShape<CommandTypeAnnotation>> {
return commandTypeAST
.filter(property => property.type === 'ObjectTypeProperty')
.map(property => buildCommandSchema(property, types, parser))
.filter(Boolean);
}
module.exports = {
getCommands,
};

View File

@@ -0,0 +1,453 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {verifyPropNotAlreadyDefined} = require('../../parsers-commons');
const {getValueFromTypes} = require('../utils.js');
// $FlowFixMe[unsupported-variance-annotation]
function getTypeAnnotationForArray(
name,
typeAnnotation,
defaultValue,
types,
parser,
buildSchema,
) {
const extractedTypeAnnotation = getValueFromTypes(typeAnnotation, types);
if (extractedTypeAnnotation.type === 'NullableTypeAnnotation') {
throw new Error(
'Nested optionals such as "$ReadOnlyArray<?boolean>" are not supported, please declare optionals at the top level of value definitions as in "?$ReadOnlyArray<boolean>"',
);
}
if (
extractedTypeAnnotation.type === 'GenericTypeAnnotation' &&
parser.getTypeAnnotationName(extractedTypeAnnotation) === 'WithDefault'
) {
throw new Error(
'Nested defaults such as "$ReadOnlyArray<WithDefault<boolean, false>>" are not supported, please declare defaults at the top level of value definitions as in "WithDefault<$ReadOnlyArray<boolean>, false>"',
);
}
if (extractedTypeAnnotation.type === 'GenericTypeAnnotation') {
// Resolve the type alias if it's not defined inline
const objectType = getValueFromTypes(extractedTypeAnnotation, types);
if (objectType.id.name === '$ReadOnly') {
return {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
objectType.typeParameters.params[0].properties,
types,
parser,
)
.map(prop => buildSchema(prop, types, parser))
.filter(Boolean),
};
}
if (objectType.id.name === '$ReadOnlyArray') {
// We need to go yet another level deeper to resolve
// types that may be defined in a type alias
const nestedObjectType = getValueFromTypes(
objectType.typeParameters.params[0],
types,
);
return {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
nestedObjectType.typeParameters.params[0].properties,
types,
parser,
)
.map(prop => buildSchema(prop, types, parser))
.filter(Boolean),
},
};
}
}
const type =
extractedTypeAnnotation.type === 'GenericTypeAnnotation'
? parser.getTypeAnnotationName(extractedTypeAnnotation)
: extractedTypeAnnotation.type;
switch (type) {
case 'ImageSource':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageSourcePrimitive',
};
case 'ImageRequest':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageRequestPrimitive',
};
case 'ColorValue':
case 'ProcessedColorValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ColorPrimitive',
};
case 'PointValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'PointPrimitive',
};
case 'EdgeInsetsValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'EdgeInsetsPrimitive',
};
case 'DimensionValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'DimensionPrimitive',
};
case 'Stringish':
return {
type: 'StringTypeAnnotation',
};
case 'Int32':
return {
type: 'Int32TypeAnnotation',
};
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
case 'Float':
return {
type: 'FloatTypeAnnotation',
};
case 'BooleanTypeAnnotation':
return {
type: 'BooleanTypeAnnotation',
};
case 'StringTypeAnnotation':
return {
type: 'StringTypeAnnotation',
};
case 'UnsafeMixed':
return {
type: 'MixedTypeAnnotation',
};
case 'UnionTypeAnnotation':
typeAnnotation.types.reduce((lastType, currType) => {
if (lastType && currType.type !== lastType.type) {
throw new Error(`Mixed types are not supported (see "${name}")`);
}
return currType;
});
if (defaultValue === null) {
throw new Error(`A default enum value is required for "${name}"`);
}
const unionType = typeAnnotation.types[0].type;
if (unionType === 'StringLiteralTypeAnnotation') {
return {
type: 'StringEnumTypeAnnotation',
default: defaultValue,
options: typeAnnotation.types.map(option => option.value),
};
} else if (unionType === 'NumberLiteralTypeAnnotation') {
throw new Error(
`Arrays of int enums are not supported (see: "${name}")`,
);
} else {
throw new Error(
`Unsupported union type for "${name}", received "${unionType}"`,
);
}
default:
throw new Error(`Unknown property type for "${name}": ${type}`);
}
}
function flattenProperties(typeDefinition, types, parser) {
return typeDefinition
.map(property => {
if (property.type === 'ObjectTypeProperty') {
return property;
} else if (property.type === 'ObjectTypeSpreadProperty') {
return flattenProperties(
parser.getProperties(property.argument.id.name, types),
types,
parser,
);
}
})
.reduce((acc, item) => {
if (Array.isArray(item)) {
item.forEach(prop => {
verifyPropNotAlreadyDefined(acc, prop);
});
return acc.concat(item);
} else {
verifyPropNotAlreadyDefined(acc, item);
acc.push(item);
return acc;
}
}, [])
.filter(Boolean);
}
// $FlowFixMe[unsupported-variance-annotation]
function getTypeAnnotation(
name,
annotation,
defaultValue,
withNullDefault,
types,
parser,
buildSchema,
) {
const typeAnnotation = getValueFromTypes(annotation, types);
if (
typeAnnotation.type === 'GenericTypeAnnotation' &&
parser.getTypeAnnotationName(typeAnnotation) === '$ReadOnlyArray'
) {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeParameters.params[0],
defaultValue,
types,
parser,
buildSchema,
),
};
}
if (
typeAnnotation.type === 'GenericTypeAnnotation' &&
parser.getTypeAnnotationName(typeAnnotation) === '$ReadOnly'
) {
return {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
typeAnnotation.typeParameters.params[0].properties,
types,
parser,
)
.map(prop => buildSchema(prop, types, parser))
.filter(Boolean),
};
}
const type =
typeAnnotation.type === 'GenericTypeAnnotation'
? parser.getTypeAnnotationName(typeAnnotation)
: typeAnnotation.type;
switch (type) {
case 'ImageSource':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageSourcePrimitive',
};
case 'ImageRequest':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageRequestPrimitive',
};
case 'ColorValue':
case 'ProcessedColorValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ColorPrimitive',
};
case 'ColorArrayValue':
return {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'ReservedPropTypeAnnotation',
name: 'ColorPrimitive',
},
};
case 'PointValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'PointPrimitive',
};
case 'EdgeInsetsValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'EdgeInsetsPrimitive',
};
case 'DimensionValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'DimensionPrimitive',
};
case 'Int32':
return {
type: 'Int32TypeAnnotation',
default: defaultValue ? defaultValue : 0,
};
case 'Double':
return {
type: 'DoubleTypeAnnotation',
default: defaultValue ? defaultValue : 0,
};
case 'Float':
return {
type: 'FloatTypeAnnotation',
default: withNullDefault
? defaultValue
: defaultValue
? defaultValue
: 0,
};
case 'BooleanTypeAnnotation':
return {
type: 'BooleanTypeAnnotation',
default: withNullDefault
? defaultValue
: defaultValue == null
? false
: defaultValue,
};
case 'StringTypeAnnotation':
if (typeof defaultValue !== 'undefined') {
return {
type: 'StringTypeAnnotation',
default: defaultValue,
};
}
throw new Error(`A default string (or null) is required for "${name}"`);
case 'Stringish':
if (typeof defaultValue !== 'undefined') {
return {
type: 'StringTypeAnnotation',
default: defaultValue,
};
}
throw new Error(`A default string (or null) is required for "${name}"`);
case 'UnionTypeAnnotation':
typeAnnotation.types.reduce((lastType, currType) => {
if (lastType && currType.type !== lastType.type) {
throw new Error(`Mixed types are not supported (see "${name}").`);
}
return currType;
});
if (defaultValue === null) {
throw new Error(`A default enum value is required for "${name}"`);
}
const unionType = typeAnnotation.types[0].type;
if (unionType === 'StringLiteralTypeAnnotation') {
return {
type: 'StringEnumTypeAnnotation',
default: defaultValue,
options: typeAnnotation.types.map(option => option.value),
};
} else if (unionType === 'NumberLiteralTypeAnnotation') {
return {
type: 'Int32EnumTypeAnnotation',
default: defaultValue,
options: typeAnnotation.types.map(option => option.value),
};
} else {
throw new Error(
`Unsupported union type for "${name}", received "${unionType}"`,
);
}
case 'ObjectTypeAnnotation':
throw new Error(
`Cannot use "${type}" type annotation for "${name}": object types must be declared using $ReadOnly<>`,
);
case 'NumberTypeAnnotation':
throw new Error(
`Cannot use "${type}" type annotation for "${name}": must use a specific numeric type like Int32, Double, or Float`,
);
case 'UnsafeMixed':
return {
type: 'MixedTypeAnnotation',
};
default:
throw new Error(
`Unknown property type for "${name}": "${type}" in the State`,
);
}
}
function getSchemaInfo(property, types, parser) {
const name = property.key.name;
const value = getValueFromTypes(property.value, types);
let typeAnnotation =
value.type === 'NullableTypeAnnotation' ? value.typeAnnotation : value;
let typeAnnotationName = parser.getTypeAnnotationName(typeAnnotation);
const optional =
value.type === 'NullableTypeAnnotation' ||
property.optional ||
(value.type === 'GenericTypeAnnotation' &&
typeAnnotationName === 'WithDefault');
if (
!property.optional &&
value.type === 'GenericTypeAnnotation' &&
typeAnnotationName === 'WithDefault'
) {
throw new Error(
`key ${name} must be optional if used with WithDefault<> annotation`,
);
}
if (
value.type === 'NullableTypeAnnotation' &&
typeAnnotation.type === 'GenericTypeAnnotation' &&
typeAnnotationName === 'WithDefault'
) {
throw new Error(
'WithDefault<> is optional and does not need to be marked as optional. Please remove the ? annotation in front of it.',
);
}
let type = typeAnnotation.type;
if (
type === 'GenericTypeAnnotation' &&
(typeAnnotationName === 'DirectEventHandler' ||
typeAnnotationName === 'BubblingEventHandler')
) {
return null;
}
if (
name === 'style' &&
type === 'GenericTypeAnnotation' &&
typeAnnotationName === 'ViewStyleProp'
) {
return null;
}
let defaultValue = null;
let withNullDefault = false;
if (
type === 'GenericTypeAnnotation' &&
typeAnnotationName === 'WithDefault'
) {
if (typeAnnotation.typeParameters.params.length === 1) {
throw new Error(
`WithDefault requires two parameters, did you forget to provide a default value for "${name}"?`,
);
}
defaultValue = typeAnnotation.typeParameters.params[1].value;
const defaultValueType = typeAnnotation.typeParameters.params[1].type;
typeAnnotation = typeAnnotation.typeParameters.params[0];
type =
typeAnnotation.type === 'GenericTypeAnnotation'
? typeAnnotationName
: typeAnnotation.type;
if (defaultValueType === 'NullLiteralTypeAnnotation') {
defaultValue = null;
withNullDefault = true;
}
}
return {
name,
optional,
typeAnnotation,
defaultValue,
withNullDefault,
};
}
module.exports = {
getSchemaInfo,
getTypeAnnotation,
flattenProperties,
};

View File

@@ -0,0 +1,496 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {BuildSchemaFN, Parser} from '../../parser';
import type {ASTNode, PropAST, TypeDeclarationMap} from '../../utils';
const {verifyPropNotAlreadyDefined} = require('../../parsers-commons');
const {getValueFromTypes} = require('../utils.js');
// $FlowFixMe[unsupported-variance-annotation]
function getTypeAnnotationForArray<+T>(
name: string,
typeAnnotation: $FlowFixMe,
defaultValue: $FlowFixMe | null,
types: TypeDeclarationMap,
parser: Parser,
buildSchema: BuildSchemaFN<T>,
): $FlowFixMe {
const extractedTypeAnnotation = getValueFromTypes(typeAnnotation, types);
if (extractedTypeAnnotation.type === 'NullableTypeAnnotation') {
throw new Error(
'Nested optionals such as "$ReadOnlyArray<?boolean>" are not supported, please declare optionals at the top level of value definitions as in "?$ReadOnlyArray<boolean>"',
);
}
if (
extractedTypeAnnotation.type === 'GenericTypeAnnotation' &&
parser.getTypeAnnotationName(extractedTypeAnnotation) === 'WithDefault'
) {
throw new Error(
'Nested defaults such as "$ReadOnlyArray<WithDefault<boolean, false>>" are not supported, please declare defaults at the top level of value definitions as in "WithDefault<$ReadOnlyArray<boolean>, false>"',
);
}
if (extractedTypeAnnotation.type === 'GenericTypeAnnotation') {
// Resolve the type alias if it's not defined inline
const objectType = getValueFromTypes(extractedTypeAnnotation, types);
if (objectType.id.name === '$ReadOnly') {
return {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
objectType.typeParameters.params[0].properties,
types,
parser,
)
.map(prop => buildSchema(prop, types, parser))
.filter(Boolean),
};
}
if (objectType.id.name === '$ReadOnlyArray') {
// We need to go yet another level deeper to resolve
// types that may be defined in a type alias
const nestedObjectType = getValueFromTypes(
objectType.typeParameters.params[0],
types,
);
return {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
nestedObjectType.typeParameters.params[0].properties,
types,
parser,
)
.map(prop => buildSchema(prop, types, parser))
.filter(Boolean),
},
};
}
}
const type =
extractedTypeAnnotation.type === 'GenericTypeAnnotation'
? parser.getTypeAnnotationName(extractedTypeAnnotation)
: extractedTypeAnnotation.type;
switch (type) {
case 'ImageSource':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageSourcePrimitive',
};
case 'ImageRequest':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageRequestPrimitive',
};
case 'ColorValue':
case 'ProcessedColorValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ColorPrimitive',
};
case 'PointValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'PointPrimitive',
};
case 'EdgeInsetsValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'EdgeInsetsPrimitive',
};
case 'DimensionValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'DimensionPrimitive',
};
case 'Stringish':
return {
type: 'StringTypeAnnotation',
};
case 'Int32':
return {
type: 'Int32TypeAnnotation',
};
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
case 'Float':
return {
type: 'FloatTypeAnnotation',
};
case 'BooleanTypeAnnotation':
return {
type: 'BooleanTypeAnnotation',
};
case 'StringTypeAnnotation':
return {
type: 'StringTypeAnnotation',
};
case 'UnsafeMixed':
return {
type: 'MixedTypeAnnotation',
};
case 'UnionTypeAnnotation':
typeAnnotation.types.reduce((lastType, currType) => {
if (lastType && currType.type !== lastType.type) {
throw new Error(`Mixed types are not supported (see "${name}")`);
}
return currType;
});
if (defaultValue === null) {
throw new Error(`A default enum value is required for "${name}"`);
}
const unionType = typeAnnotation.types[0].type;
if (unionType === 'StringLiteralTypeAnnotation') {
return {
type: 'StringEnumTypeAnnotation',
default: (defaultValue: string),
options: typeAnnotation.types.map(option => option.value),
};
} else if (unionType === 'NumberLiteralTypeAnnotation') {
throw new Error(
`Arrays of int enums are not supported (see: "${name}")`,
);
} else {
throw new Error(
`Unsupported union type for "${name}", received "${unionType}"`,
);
}
default:
throw new Error(`Unknown property type for "${name}": ${type}`);
}
}
function flattenProperties(
typeDefinition: $ReadOnlyArray<PropAST>,
types: TypeDeclarationMap,
parser: Parser,
): $ReadOnlyArray<PropAST> {
return typeDefinition
.map(property => {
if (property.type === 'ObjectTypeProperty') {
return property;
} else if (property.type === 'ObjectTypeSpreadProperty') {
return flattenProperties(
parser.getProperties(property.argument.id.name, types),
types,
parser,
);
}
})
.reduce((acc: Array<PropAST>, item) => {
if (Array.isArray(item)) {
item.forEach(prop => {
verifyPropNotAlreadyDefined(acc, prop);
});
return acc.concat(item);
} else {
verifyPropNotAlreadyDefined(acc, item);
acc.push(item);
return acc;
}
}, [])
.filter(Boolean);
}
// $FlowFixMe[unsupported-variance-annotation]
function getTypeAnnotation<+T>(
name: string,
annotation: $FlowFixMe | ASTNode,
defaultValue: $FlowFixMe | null,
withNullDefault: boolean,
types: TypeDeclarationMap,
parser: Parser,
buildSchema: BuildSchemaFN<T>,
): $FlowFixMe {
const typeAnnotation = getValueFromTypes(annotation, types);
if (
typeAnnotation.type === 'GenericTypeAnnotation' &&
parser.getTypeAnnotationName(typeAnnotation) === '$ReadOnlyArray'
) {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeParameters.params[0],
defaultValue,
types,
parser,
buildSchema,
),
};
}
if (
typeAnnotation.type === 'GenericTypeAnnotation' &&
parser.getTypeAnnotationName(typeAnnotation) === '$ReadOnly'
) {
return {
type: 'ObjectTypeAnnotation',
properties: flattenProperties(
typeAnnotation.typeParameters.params[0].properties,
types,
parser,
)
.map(prop => buildSchema(prop, types, parser))
.filter(Boolean),
};
}
const type =
typeAnnotation.type === 'GenericTypeAnnotation'
? parser.getTypeAnnotationName(typeAnnotation)
: typeAnnotation.type;
switch (type) {
case 'ImageSource':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageSourcePrimitive',
};
case 'ImageRequest':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageRequestPrimitive',
};
case 'ColorValue':
case 'ProcessedColorValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ColorPrimitive',
};
case 'ColorArrayValue':
return {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'ReservedPropTypeAnnotation',
name: 'ColorPrimitive',
},
};
case 'PointValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'PointPrimitive',
};
case 'EdgeInsetsValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'EdgeInsetsPrimitive',
};
case 'DimensionValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'DimensionPrimitive',
};
case 'Int32':
return {
type: 'Int32TypeAnnotation',
default: ((defaultValue ? defaultValue : 0): number),
};
case 'Double':
return {
type: 'DoubleTypeAnnotation',
default: ((defaultValue ? defaultValue : 0): number),
};
case 'Float':
return {
type: 'FloatTypeAnnotation',
default: withNullDefault
? (defaultValue: number | null)
: ((defaultValue ? defaultValue : 0): number),
};
case 'BooleanTypeAnnotation':
return {
type: 'BooleanTypeAnnotation',
default: withNullDefault
? (defaultValue: boolean | null)
: ((defaultValue == null ? false : defaultValue): boolean),
};
case 'StringTypeAnnotation':
if (typeof defaultValue !== 'undefined') {
return {
type: 'StringTypeAnnotation',
default: (defaultValue: string | null),
};
}
throw new Error(`A default string (or null) is required for "${name}"`);
case 'Stringish':
if (typeof defaultValue !== 'undefined') {
return {
type: 'StringTypeAnnotation',
default: (defaultValue: string | null),
};
}
throw new Error(`A default string (or null) is required for "${name}"`);
case 'UnionTypeAnnotation':
typeAnnotation.types.reduce((lastType, currType) => {
if (lastType && currType.type !== lastType.type) {
throw new Error(`Mixed types are not supported (see "${name}").`);
}
return currType;
});
if (defaultValue === null) {
throw new Error(`A default enum value is required for "${name}"`);
}
const unionType = typeAnnotation.types[0].type;
if (unionType === 'StringLiteralTypeAnnotation') {
return {
type: 'StringEnumTypeAnnotation',
default: (defaultValue: string),
options: typeAnnotation.types.map(option => option.value),
};
} else if (unionType === 'NumberLiteralTypeAnnotation') {
return {
type: 'Int32EnumTypeAnnotation',
default: (defaultValue: number),
options: typeAnnotation.types.map(option => option.value),
};
} else {
throw new Error(
`Unsupported union type for "${name}", received "${unionType}"`,
);
}
case 'ObjectTypeAnnotation':
throw new Error(
`Cannot use "${type}" type annotation for "${name}": object types must be declared using $ReadOnly<>`,
);
case 'NumberTypeAnnotation':
throw new Error(
`Cannot use "${type}" type annotation for "${name}": must use a specific numeric type like Int32, Double, or Float`,
);
case 'UnsafeMixed':
return {
type: 'MixedTypeAnnotation',
};
default:
throw new Error(
`Unknown property type for "${name}": "${type}" in the State`,
);
}
}
type SchemaInfo = {
name: string,
optional: boolean,
typeAnnotation: $FlowFixMe,
defaultValue: $FlowFixMe,
withNullDefault: boolean,
};
function getSchemaInfo(
property: PropAST,
types: TypeDeclarationMap,
parser: Parser,
): ?SchemaInfo {
const name = property.key.name;
const value = getValueFromTypes(property.value, types);
let typeAnnotation =
value.type === 'NullableTypeAnnotation' ? value.typeAnnotation : value;
let typeAnnotationName = parser.getTypeAnnotationName(typeAnnotation);
const optional =
value.type === 'NullableTypeAnnotation' ||
property.optional ||
(value.type === 'GenericTypeAnnotation' &&
typeAnnotationName === 'WithDefault');
if (
!property.optional &&
value.type === 'GenericTypeAnnotation' &&
typeAnnotationName === 'WithDefault'
) {
throw new Error(
`key ${name} must be optional if used with WithDefault<> annotation`,
);
}
if (
value.type === 'NullableTypeAnnotation' &&
typeAnnotation.type === 'GenericTypeAnnotation' &&
typeAnnotationName === 'WithDefault'
) {
throw new Error(
'WithDefault<> is optional and does not need to be marked as optional. Please remove the ? annotation in front of it.',
);
}
let type = typeAnnotation.type;
if (
type === 'GenericTypeAnnotation' &&
(typeAnnotationName === 'DirectEventHandler' ||
typeAnnotationName === 'BubblingEventHandler')
) {
return null;
}
if (
name === 'style' &&
type === 'GenericTypeAnnotation' &&
typeAnnotationName === 'ViewStyleProp'
) {
return null;
}
let defaultValue = null;
let withNullDefault = false;
if (
type === 'GenericTypeAnnotation' &&
typeAnnotationName === 'WithDefault'
) {
if (typeAnnotation.typeParameters.params.length === 1) {
throw new Error(
`WithDefault requires two parameters, did you forget to provide a default value for "${name}"?`,
);
}
defaultValue = typeAnnotation.typeParameters.params[1].value;
const defaultValueType = typeAnnotation.typeParameters.params[1].type;
typeAnnotation = typeAnnotation.typeParameters.params[0];
type =
typeAnnotation.type === 'GenericTypeAnnotation'
? typeAnnotationName
: typeAnnotation.type;
if (defaultValueType === 'NullLiteralTypeAnnotation') {
defaultValue = null;
withNullDefault = true;
}
}
return {
name,
optional,
typeAnnotation,
defaultValue,
withNullDefault,
};
}
module.exports = {
getSchemaInfo,
getTypeAnnotation,
flattenProperties,
};

View File

@@ -0,0 +1,249 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {
throwIfArgumentPropsAreNull,
throwIfBubblingTypeIsNull,
throwIfEventHasNoName,
} = require('../../error-utils');
const {
buildPropertiesForEvent,
emitBuildEventSchema,
getEventArgument,
handleEventHandler,
} = require('../../parsers-commons');
const {
emitBoolProp,
emitDoubleProp,
emitFloatProp,
emitInt32Prop,
emitMixedProp,
emitObjectProp,
emitStringProp,
emitUnionProp,
} = require('../../parsers-primitives');
function getPropertyType(
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
name,
optional,
typeAnnotation,
parser,
) {
const type = extractTypeFromTypeAnnotation(typeAnnotation, parser);
switch (type) {
case 'BooleanTypeAnnotation':
return emitBoolProp(name, optional);
case 'StringTypeAnnotation':
return emitStringProp(name, optional);
case 'Int32':
return emitInt32Prop(name, optional);
case 'Double':
return emitDoubleProp(name, optional);
case 'Float':
return emitFloatProp(name, optional);
case '$ReadOnly':
return getPropertyType(
name,
optional,
typeAnnotation.typeParameters.params[0],
parser,
);
case 'ObjectTypeAnnotation':
return emitObjectProp(
name,
optional,
parser,
typeAnnotation,
extractArrayElementType,
);
case 'UnionTypeAnnotation':
return emitUnionProp(name, optional, parser, typeAnnotation);
case 'UnsafeMixed':
return emitMixedProp(name, optional);
case 'ArrayTypeAnnotation':
case '$ReadOnlyArray':
return {
name,
optional,
typeAnnotation: extractArrayElementType(typeAnnotation, name, parser),
};
default:
throw new Error(`Unable to determine event type for "${name}": ${type}`);
}
}
function extractArrayElementType(typeAnnotation, name, parser) {
const type = extractTypeFromTypeAnnotation(typeAnnotation, parser);
switch (type) {
case 'BooleanTypeAnnotation':
return {
type: 'BooleanTypeAnnotation',
};
case 'StringTypeAnnotation':
return {
type: 'StringTypeAnnotation',
};
case 'Int32':
return {
type: 'Int32TypeAnnotation',
};
case 'Float':
return {
type: 'FloatTypeAnnotation',
};
case 'NumberTypeAnnotation':
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
case 'UnionTypeAnnotation':
return {
type: 'StringLiteralUnionTypeAnnotation',
types: typeAnnotation.types.map(option => ({
type: 'StringLiteralTypeAnnotation',
value: parser.getLiteralValue(option),
})),
};
case 'UnsafeMixed':
return {
type: 'MixedTypeAnnotation',
};
case 'ObjectTypeAnnotation':
return {
type: 'ObjectTypeAnnotation',
properties: parser
.getObjectProperties(typeAnnotation)
.map(member =>
buildPropertiesForEvent(member, parser, getPropertyType),
),
};
case 'ArrayTypeAnnotation':
return {
type: 'ArrayTypeAnnotation',
elementType: extractArrayElementType(
typeAnnotation.elementType,
name,
parser,
),
};
case '$ReadOnlyArray':
const genericParams = typeAnnotation.typeParameters.params;
if (genericParams.length !== 1) {
throw new Error(
`Events only supports arrays with 1 Generic type. Found ${genericParams.length} types:\n${prettify(genericParams)}`,
);
}
return {
type: 'ArrayTypeAnnotation',
elementType: extractArrayElementType(genericParams[0], name, parser),
};
default:
throw new Error(
`Unrecognized ${type} for Array ${name} in events.\n${prettify(typeAnnotation)}`,
);
}
}
function prettify(jsonObject) {
return JSON.stringify(jsonObject, null, 2);
}
function extractTypeFromTypeAnnotation(typeAnnotation, parser) {
return typeAnnotation.type === 'GenericTypeAnnotation'
? parser.getTypeAnnotationName(typeAnnotation)
: typeAnnotation.type;
}
function findEventArgumentsAndType(
parser,
typeAnnotation,
types,
bubblingType,
paperName,
) {
throwIfEventHasNoName(typeAnnotation, parser);
const name = parser.getTypeAnnotationName(typeAnnotation);
if (name === '$ReadOnly') {
return {
argumentProps: typeAnnotation.typeParameters.params[0].properties,
paperTopLevelNameDeprecated: paperName,
bubblingType,
};
} else if (name === 'BubblingEventHandler' || name === 'DirectEventHandler') {
return handleEventHandler(
name,
typeAnnotation,
parser,
types,
findEventArgumentsAndType,
);
} else if (types[name]) {
return findEventArgumentsAndType(
parser,
types[name].right,
types,
bubblingType,
paperName,
);
} else {
return {
argumentProps: null,
bubblingType: null,
paperTopLevelNameDeprecated: null,
};
}
}
function buildEventSchema(types, property, parser) {
const name = property.key.name;
const optional =
property.optional || property.value.type === 'NullableTypeAnnotation';
let typeAnnotation =
property.value.type === 'NullableTypeAnnotation'
? property.value.typeAnnotation
: property.value;
if (
typeAnnotation.type !== 'GenericTypeAnnotation' ||
(parser.getTypeAnnotationName(typeAnnotation) !== 'BubblingEventHandler' &&
parser.getTypeAnnotationName(typeAnnotation) !== 'DirectEventHandler')
) {
return null;
}
const {argumentProps, bubblingType, paperTopLevelNameDeprecated} =
findEventArgumentsAndType(parser, typeAnnotation, types);
const nonNullableArgumentProps = throwIfArgumentPropsAreNull(
argumentProps,
name,
);
const nonNullableBubblingType = throwIfBubblingTypeIsNull(bubblingType, name);
const argument = getEventArgument(
nonNullableArgumentProps,
parser,
getPropertyType,
);
return emitBuildEventSchema(
paperTopLevelNameDeprecated,
name,
optional,
nonNullableBubblingType,
argument,
);
}
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
function getEvents(eventTypeAST, types, parser) {
return eventTypeAST
.filter(property => property.type === 'ObjectTypeProperty')
.map(property => buildEventSchema(types, property, parser))
.filter(Boolean);
}
module.exports = {
getEvents,
extractArrayElementType,
};

View File

@@ -0,0 +1,288 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
EventTypeAnnotation,
EventTypeShape,
NamedShape,
} from '../../../CodegenSchema.js';
import type {Parser} from '../../parser';
import type {EventArgumentReturnType} from '../../parsers-commons';
const {
throwIfArgumentPropsAreNull,
throwIfBubblingTypeIsNull,
throwIfEventHasNoName,
} = require('../../error-utils');
const {
buildPropertiesForEvent,
emitBuildEventSchema,
getEventArgument,
handleEventHandler,
} = require('../../parsers-commons');
const {
emitBoolProp,
emitDoubleProp,
emitFloatProp,
emitInt32Prop,
emitMixedProp,
emitObjectProp,
emitStringProp,
emitUnionProp,
} = require('../../parsers-primitives');
function getPropertyType(
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
name: string,
optional: boolean,
typeAnnotation: $FlowFixMe,
parser: Parser,
): NamedShape<EventTypeAnnotation> {
const type = extractTypeFromTypeAnnotation(typeAnnotation, parser);
switch (type) {
case 'BooleanTypeAnnotation':
return emitBoolProp(name, optional);
case 'StringTypeAnnotation':
return emitStringProp(name, optional);
case 'Int32':
return emitInt32Prop(name, optional);
case 'Double':
return emitDoubleProp(name, optional);
case 'Float':
return emitFloatProp(name, optional);
case '$ReadOnly':
return getPropertyType(
name,
optional,
typeAnnotation.typeParameters.params[0],
parser,
);
case 'ObjectTypeAnnotation':
return emitObjectProp(
name,
optional,
parser,
typeAnnotation,
extractArrayElementType,
);
case 'UnionTypeAnnotation':
return emitUnionProp(name, optional, parser, typeAnnotation);
case 'UnsafeMixed':
return emitMixedProp(name, optional);
case 'ArrayTypeAnnotation':
case '$ReadOnlyArray':
return {
name,
optional,
typeAnnotation: extractArrayElementType(typeAnnotation, name, parser),
};
default:
throw new Error(`Unable to determine event type for "${name}": ${type}`);
}
}
function extractArrayElementType(
typeAnnotation: $FlowFixMe,
name: string,
parser: Parser,
): EventTypeAnnotation {
const type = extractTypeFromTypeAnnotation(typeAnnotation, parser);
switch (type) {
case 'BooleanTypeAnnotation':
return {type: 'BooleanTypeAnnotation'};
case 'StringTypeAnnotation':
return {type: 'StringTypeAnnotation'};
case 'Int32':
return {type: 'Int32TypeAnnotation'};
case 'Float':
return {type: 'FloatTypeAnnotation'};
case 'NumberTypeAnnotation':
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
case 'UnionTypeAnnotation':
return {
type: 'StringLiteralUnionTypeAnnotation',
types: typeAnnotation.types.map(option => ({
type: 'StringLiteralTypeAnnotation',
value: parser.getLiteralValue(option),
})),
};
case 'UnsafeMixed':
return {type: 'MixedTypeAnnotation'};
case 'ObjectTypeAnnotation':
return {
type: 'ObjectTypeAnnotation',
properties: parser
.getObjectProperties(typeAnnotation)
.map(member =>
buildPropertiesForEvent(member, parser, getPropertyType),
),
};
case 'ArrayTypeAnnotation':
return {
type: 'ArrayTypeAnnotation',
elementType: extractArrayElementType(
typeAnnotation.elementType,
name,
parser,
),
};
case '$ReadOnlyArray':
const genericParams = typeAnnotation.typeParameters.params;
if (genericParams.length !== 1) {
throw new Error(
`Events only supports arrays with 1 Generic type. Found ${
genericParams.length
} types:\n${prettify(genericParams)}`,
);
}
return {
type: 'ArrayTypeAnnotation',
elementType: extractArrayElementType(genericParams[0], name, parser),
};
default:
throw new Error(
`Unrecognized ${type} for Array ${name} in events.\n${prettify(
typeAnnotation,
)}`,
);
}
}
function prettify(jsonObject: $FlowFixMe): string {
return JSON.stringify(jsonObject, null, 2);
}
function extractTypeFromTypeAnnotation(
typeAnnotation: $FlowFixMe,
parser: Parser,
): string {
return typeAnnotation.type === 'GenericTypeAnnotation'
? parser.getTypeAnnotationName(typeAnnotation)
: typeAnnotation.type;
}
function findEventArgumentsAndType(
parser: Parser,
typeAnnotation: $FlowFixMe,
types: TypeMap,
bubblingType: void | 'direct' | 'bubble',
paperName: ?$FlowFixMe,
): EventArgumentReturnType {
throwIfEventHasNoName(typeAnnotation, parser);
const name = parser.getTypeAnnotationName(typeAnnotation);
if (name === '$ReadOnly') {
return {
argumentProps: typeAnnotation.typeParameters.params[0].properties,
paperTopLevelNameDeprecated: paperName,
bubblingType,
};
} else if (name === 'BubblingEventHandler' || name === 'DirectEventHandler') {
return handleEventHandler(
name,
typeAnnotation,
parser,
types,
findEventArgumentsAndType,
);
} else if (types[name]) {
return findEventArgumentsAndType(
parser,
types[name].right,
types,
bubblingType,
paperName,
);
} else {
return {
argumentProps: null,
bubblingType: null,
paperTopLevelNameDeprecated: null,
};
}
}
function buildEventSchema(
types: TypeMap,
property: EventTypeAST,
parser: Parser,
): ?EventTypeShape {
const name = property.key.name;
const optional =
property.optional || property.value.type === 'NullableTypeAnnotation';
let typeAnnotation =
property.value.type === 'NullableTypeAnnotation'
? property.value.typeAnnotation
: property.value;
if (
typeAnnotation.type !== 'GenericTypeAnnotation' ||
(parser.getTypeAnnotationName(typeAnnotation) !== 'BubblingEventHandler' &&
parser.getTypeAnnotationName(typeAnnotation) !== 'DirectEventHandler')
) {
return null;
}
const {argumentProps, bubblingType, paperTopLevelNameDeprecated} =
findEventArgumentsAndType(parser, typeAnnotation, types);
const nonNullableArgumentProps = throwIfArgumentPropsAreNull(
argumentProps,
name,
);
const nonNullableBubblingType = throwIfBubblingTypeIsNull(bubblingType, name);
const argument = getEventArgument(
nonNullableArgumentProps,
parser,
getPropertyType,
);
return emitBuildEventSchema(
paperTopLevelNameDeprecated,
name,
optional,
nonNullableBubblingType,
argument,
);
}
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
type EventTypeAST = Object;
type TypeMap = {
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
[string]: Object,
...
};
function getEvents(
eventTypeAST: $ReadOnlyArray<EventTypeAST>,
types: TypeMap,
parser: Parser,
): $ReadOnlyArray<EventTypeShape> {
return eventTypeAST
.filter(property => property.type === 'ObjectTypeProperty')
.map(property => buildEventSchema(types, property, parser))
.filter(Boolean);
}
module.exports = {
getEvents,
extractArrayElementType,
};

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {
findComponentConfig,
getCommandProperties,
getOptions,
} = require('../../parsers-commons');
const {getCommands} = require('./commands');
const {getEvents} = require('./events');
// $FlowFixMe[signature-verification-failure] there's no flowtype for AST
function buildComponentSchema(ast, parser) {
const {componentName, propsTypeName, optionsExpression} = findComponentConfig(
ast,
parser,
);
const types = parser.getTypes(ast);
const propProperties = parser.getProperties(propsTypeName, types);
const commandProperties = getCommandProperties(ast, parser);
const {extendsProps, props} = parser.getProps(propProperties, types);
const options = getOptions(optionsExpression);
const events = getEvents(propProperties, types, parser);
const commands = getCommands(commandProperties, types, parser);
return {
filename: componentName,
componentName,
options,
extendsProps,
events,
props,
commands,
};
}
module.exports = {
buildComponentSchema,
};

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {Parser} from '../../parser';
import type {ComponentSchemaBuilderConfig} from '../../schema.js';
const {
findComponentConfig,
getCommandProperties,
getOptions,
} = require('../../parsers-commons');
const {getCommands} = require('./commands');
const {getEvents} = require('./events');
// $FlowFixMe[signature-verification-failure] there's no flowtype for AST
function buildComponentSchema(
ast: $FlowFixMe,
parser: Parser,
): ComponentSchemaBuilderConfig {
const {componentName, propsTypeName, optionsExpression} = findComponentConfig(
ast,
parser,
);
const types = parser.getTypes(ast);
const propProperties = parser.getProperties(propsTypeName, types);
const commandProperties = getCommandProperties(ast, parser);
const {extendsProps, props} = parser.getProps(propProperties, types);
const options = getOptions(optionsExpression);
const events = getEvents(propProperties, types, parser);
const commands = getCommands(commandProperties, types, parser);
return {
filename: componentName,
componentName,
options,
extendsProps,
events,
props,
commands,
};
}
module.exports = {
buildComponentSchema,
};

View File

@@ -0,0 +1,289 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {
UnsupportedEnumDeclarationParserError,
UnsupportedGenericParserError,
UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError,
UnsupportedTypeAnnotationParserError,
} = require('../../errors');
const {
assertGenericTypeAnnotationHasExactlyOneTypeParameter,
parseObjectProperty,
unwrapNullable,
wrapNullable,
} = require('../../parsers-commons');
const {
emitArrayType,
emitCommonTypes,
emitDictionary,
emitFunction,
emitNumberLiteral,
emitPromise,
emitRootTag,
emitUnion,
translateArrayTypeAnnotation,
typeAliasResolution,
typeEnumResolution,
} = require('../../parsers-primitives');
function translateTypeAnnotation(
hasteModuleName,
/**
* TODO(T71778680): Flow-type this node.
*/
flowTypeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
) {
const resolveTypeAnnotationFN = parser.getResolveTypeAnnotationFN();
const {nullable, typeAnnotation, typeResolutionStatus} =
resolveTypeAnnotationFN(flowTypeAnnotation, types, parser);
switch (typeAnnotation.type) {
case 'ArrayTypeAnnotation': {
return translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
enumMap,
cxxOnly,
'Array',
typeAnnotation.elementType,
nullable,
translateTypeAnnotation,
parser,
);
}
case 'GenericTypeAnnotation': {
switch (parser.getTypeAnnotationName(typeAnnotation)) {
case 'RootTag': {
return emitRootTag(nullable);
}
case 'Promise': {
return emitPromise(
hasteModuleName,
typeAnnotation,
parser,
nullable,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
);
}
case 'Array':
case '$ReadOnlyArray': {
return emitArrayType(
hasteModuleName,
typeAnnotation,
parser,
types,
aliasMap,
enumMap,
cxxOnly,
nullable,
translateTypeAnnotation,
);
}
case '$ReadOnly': {
assertGenericTypeAnnotationHasExactlyOneTypeParameter(
hasteModuleName,
typeAnnotation,
parser,
);
const [paramType, isParamNullable] = unwrapNullable(
translateTypeAnnotation(
hasteModuleName,
typeAnnotation.typeParameters.params[0],
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
),
);
return wrapNullable(nullable || isParamNullable, paramType);
}
default: {
const commonType = emitCommonTypes(
hasteModuleName,
types,
typeAnnotation,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
parser,
);
if (!commonType) {
throw new UnsupportedGenericParserError(
hasteModuleName,
typeAnnotation,
parser,
);
}
return commonType;
}
}
}
case 'ObjectTypeAnnotation': {
// if there is any indexer, then it is a dictionary
if (typeAnnotation.indexers) {
const indexers = typeAnnotation.indexers.filter(
member => member.type === 'ObjectTypeIndexer',
);
if (indexers.length > 0 && typeAnnotation.properties.length > 0) {
throw new UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
);
}
if (indexers.length > 0) {
// check the property type to prevent developers from using unsupported types
// the return value from `translateTypeAnnotation` is unused
const propertyType = indexers[0].value;
const valueType = translateTypeAnnotation(
hasteModuleName,
propertyType,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
// no need to do further checking
return emitDictionary(nullable, valueType);
}
}
const objectTypeAnnotation = {
type: 'ObjectTypeAnnotation',
// $FlowFixMe[missing-type-arg]
properties: [...typeAnnotation.properties, ...typeAnnotation.indexers]
.map(property => {
return tryParse(() => {
return parseObjectProperty(
flowTypeAnnotation,
property,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
translateTypeAnnotation,
parser,
);
});
})
.filter(Boolean),
};
return typeAliasResolution(
typeResolutionStatus,
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
objectTypeAnnotation,
aliasMap,
nullable,
);
}
case 'FunctionTypeAnnotation': {
return emitFunction(
nullable,
hasteModuleName,
typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
parser,
);
}
case 'UnionTypeAnnotation': {
return emitUnion(nullable, hasteModuleName, typeAnnotation, parser);
}
case 'NumberLiteralTypeAnnotation': {
return emitNumberLiteral(nullable, typeAnnotation.value);
}
case 'StringLiteralTypeAnnotation': {
return wrapNullable(nullable, {
type: 'StringLiteralTypeAnnotation',
value: typeAnnotation.value,
});
}
case 'EnumStringBody':
case 'EnumNumberBody': {
if (
typeAnnotation.type === 'EnumNumberBody' &&
typeAnnotation.members.some(m => {
var _m$init;
return (
m.type === 'EnumNumberMember' &&
!Number.isInteger(
(_m$init = m.init) === null || _m$init === void 0
? void 0
: _m$init.value,
)
);
})
) {
throw new UnsupportedEnumDeclarationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
return typeEnumResolution(
typeAnnotation,
typeResolutionStatus,
nullable,
hasteModuleName,
enumMap,
parser,
);
}
default: {
const commonType = emitCommonTypes(
hasteModuleName,
types,
typeAnnotation,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
parser,
);
if (!commonType) {
throw new UnsupportedTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
return commonType;
}
}
}
module.exports = {
flowTranslateTypeAnnotation: translateTypeAnnotation,
};

View File

@@ -0,0 +1,309 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
NamedShape,
NativeModuleAliasMap,
NativeModuleBaseTypeAnnotation,
NativeModuleEnumMap,
NativeModuleTypeAnnotation,
Nullable,
} from '../../../CodegenSchema';
import type {Parser} from '../../parser';
import type {ParserErrorCapturer, TypeDeclarationMap} from '../../utils';
const {
UnsupportedEnumDeclarationParserError,
UnsupportedGenericParserError,
UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError,
UnsupportedTypeAnnotationParserError,
} = require('../../errors');
const {
assertGenericTypeAnnotationHasExactlyOneTypeParameter,
parseObjectProperty,
unwrapNullable,
wrapNullable,
} = require('../../parsers-commons');
const {
emitArrayType,
emitCommonTypes,
emitDictionary,
emitFunction,
emitNumberLiteral,
emitPromise,
emitRootTag,
emitUnion,
translateArrayTypeAnnotation,
typeAliasResolution,
typeEnumResolution,
} = require('../../parsers-primitives');
function translateTypeAnnotation(
hasteModuleName: string,
/**
* TODO(T71778680): Flow-type this node.
*/
flowTypeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
parser: Parser,
): Nullable<NativeModuleTypeAnnotation> {
const resolveTypeAnnotationFN = parser.getResolveTypeAnnotationFN();
const {nullable, typeAnnotation, typeResolutionStatus} =
resolveTypeAnnotationFN(flowTypeAnnotation, types, parser);
switch (typeAnnotation.type) {
case 'ArrayTypeAnnotation': {
return translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
enumMap,
cxxOnly,
'Array',
typeAnnotation.elementType,
nullable,
translateTypeAnnotation,
parser,
);
}
case 'GenericTypeAnnotation': {
switch (parser.getTypeAnnotationName(typeAnnotation)) {
case 'RootTag': {
return emitRootTag(nullable);
}
case 'Promise': {
return emitPromise(
hasteModuleName,
typeAnnotation,
parser,
nullable,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
);
}
case 'Array':
case '$ReadOnlyArray': {
return emitArrayType(
hasteModuleName,
typeAnnotation,
parser,
types,
aliasMap,
enumMap,
cxxOnly,
nullable,
translateTypeAnnotation,
);
}
case '$ReadOnly': {
assertGenericTypeAnnotationHasExactlyOneTypeParameter(
hasteModuleName,
typeAnnotation,
parser,
);
const [paramType, isParamNullable] = unwrapNullable(
translateTypeAnnotation(
hasteModuleName,
typeAnnotation.typeParameters.params[0],
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
),
);
return wrapNullable(nullable || isParamNullable, paramType);
}
default: {
const commonType = emitCommonTypes(
hasteModuleName,
types,
typeAnnotation,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
parser,
);
if (!commonType) {
throw new UnsupportedGenericParserError(
hasteModuleName,
typeAnnotation,
parser,
);
}
return commonType;
}
}
}
case 'ObjectTypeAnnotation': {
// if there is any indexer, then it is a dictionary
if (typeAnnotation.indexers) {
const indexers = typeAnnotation.indexers.filter(
member => member.type === 'ObjectTypeIndexer',
);
if (indexers.length > 0 && typeAnnotation.properties.length > 0) {
throw new UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
);
}
if (indexers.length > 0) {
// check the property type to prevent developers from using unsupported types
// the return value from `translateTypeAnnotation` is unused
const propertyType = indexers[0].value;
const valueType = translateTypeAnnotation(
hasteModuleName,
propertyType,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
// no need to do further checking
return emitDictionary(nullable, valueType);
}
}
const objectTypeAnnotation = {
type: 'ObjectTypeAnnotation',
// $FlowFixMe[missing-type-arg]
properties: ([
...typeAnnotation.properties,
...typeAnnotation.indexers,
]: Array<$FlowFixMe>)
.map<?NamedShape<Nullable<NativeModuleBaseTypeAnnotation>>>(
property => {
return tryParse(() => {
return parseObjectProperty(
flowTypeAnnotation,
property,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
translateTypeAnnotation,
parser,
);
});
},
)
.filter(Boolean),
};
return typeAliasResolution(
typeResolutionStatus,
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
objectTypeAnnotation,
aliasMap,
nullable,
);
}
case 'FunctionTypeAnnotation': {
return emitFunction(
nullable,
hasteModuleName,
typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
parser,
);
}
case 'UnionTypeAnnotation': {
return emitUnion(nullable, hasteModuleName, typeAnnotation, parser);
}
case 'NumberLiteralTypeAnnotation': {
return emitNumberLiteral(nullable, typeAnnotation.value);
}
case 'StringLiteralTypeAnnotation': {
return wrapNullable(nullable, {
type: 'StringLiteralTypeAnnotation',
value: typeAnnotation.value,
});
}
case 'EnumStringBody':
case 'EnumNumberBody': {
if (
typeAnnotation.type === 'EnumNumberBody' &&
typeAnnotation.members.some(
m =>
m.type === 'EnumNumberMember' && !Number.isInteger(m.init?.value),
)
) {
throw new UnsupportedEnumDeclarationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
return typeEnumResolution(
typeAnnotation,
typeResolutionStatus,
nullable,
hasteModuleName,
enumMap,
parser,
);
}
default: {
const commonType = emitCommonTypes(
hasteModuleName,
types,
typeAnnotation,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
parser,
);
if (!commonType) {
throw new UnsupportedTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
return commonType;
}
}
}
module.exports = {
flowTranslateTypeAnnotation: translateTypeAnnotation,
};

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const hermesParser = require('hermes-parser');
function parseFlowAndThrowErrors(code, options = {}) {
let ast;
try {
ast = hermesParser.parse(code, {
// Produce an ESTree-compliant AST
babel: false,
// Parse Flow without a pragma
flow: 'all',
reactRuntimeTarget: '19',
...(options.filename != null
? {
sourceFilename: options.filename,
}
: {}),
});
} catch (e) {
if (options.filename != null) {
e.message = `Syntax error in ${options.filename}: ${e.message}`;
}
throw e;
}
return ast;
}
module.exports = {
parseFlowAndThrowErrors,
};

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {Program as ESTreeProgram} from 'hermes-estree';
const hermesParser = require('hermes-parser');
function parseFlowAndThrowErrors(
code: string,
options: $ReadOnly<{filename?: ?string}> = {},
): ESTreeProgram {
let ast;
try {
ast = hermesParser.parse(code, {
// Produce an ESTree-compliant AST
babel: false,
// Parse Flow without a pragma
flow: 'all',
reactRuntimeTarget: '19',
...(options.filename != null ? {sourceFilename: options.filename} : {}),
});
} catch (e) {
if (options.filename != null) {
e.message = `Syntax error in ${options.filename}: ${e.message}`;
}
throw e;
}
return ast;
}
module.exports = {
parseFlowAndThrowErrors,
};

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { Parser } from '../parser';
import type { SchemaType } from '../../CodegenSchema';
import type { ParserType } from '../errors';
export declare class FlowParser implements Parser {
language(): ParserType;
parseFile(filename: string): SchemaType;
parseString(contents: string, filename?: string): SchemaType;
parseModuleFixture(filename: string): SchemaType;
}

View File

@@ -0,0 +1,504 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function _defineProperty(e, r, t) {
return (
(r = _toPropertyKey(r)) in e
? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0,
})
: (e[r] = t),
e
);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, 'string');
return 'symbol' == typeof i ? i : i + '';
}
function _toPrimitive(t, r) {
if ('object' != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || 'default');
if ('object' != typeof i) return i;
throw new TypeError('@@toPrimitive must return a primitive value.');
}
return ('string' === r ? String : Number)(t);
}
const {
UnsupportedObjectPropertyTypeAnnotationParserError,
} = require('../errors');
const {
buildModuleSchema,
buildPropSchema,
buildSchema,
handleGenericTypeAnnotation,
} = require('../parsers-commons');
const {Visitor} = require('../parsers-primitives');
const {wrapComponentSchema} = require('../schema.js');
const {buildComponentSchema} = require('./components');
const {
flattenProperties,
getSchemaInfo,
getTypeAnnotation,
} = require('./components/componentsUtils');
const {flowTranslateTypeAnnotation} = require('./modules');
const {parseFlowAndThrowErrors} = require('./parseFlowAndThrowErrors');
const fs = require('fs');
const invariant = require('invariant');
class FlowParser {
constructor() {
_defineProperty(
this,
'typeParameterInstantiation',
'TypeParameterInstantiation',
);
_defineProperty(this, 'typeAlias', 'TypeAlias');
_defineProperty(this, 'enumDeclaration', 'EnumDeclaration');
_defineProperty(this, 'interfaceDeclaration', 'InterfaceDeclaration');
_defineProperty(
this,
'nullLiteralTypeAnnotation',
'NullLiteralTypeAnnotation',
);
_defineProperty(
this,
'undefinedLiteralTypeAnnotation',
'VoidLiteralTypeAnnotation',
);
}
isProperty(property) {
return property.type === 'ObjectTypeProperty';
}
getKeyName(property, hasteModuleName) {
if (!this.isProperty(property)) {
throw new UnsupportedObjectPropertyTypeAnnotationParserError(
hasteModuleName,
property,
property.type,
this.language(),
);
}
return property.key.name;
}
language() {
return 'Flow';
}
getTypeAnnotationName(typeAnnotation) {
var _typeAnnotation$id, _typeAnnotation$id2;
if (
(typeAnnotation === null ||
typeAnnotation === void 0 ||
(_typeAnnotation$id = typeAnnotation.id) === null ||
_typeAnnotation$id === void 0
? void 0
: _typeAnnotation$id.type) === 'QualifiedTypeIdentifier'
) {
return typeAnnotation.id.id.name;
}
return typeAnnotation === null ||
typeAnnotation === void 0 ||
(_typeAnnotation$id2 = typeAnnotation.id) === null ||
_typeAnnotation$id2 === void 0
? void 0
: _typeAnnotation$id2.name;
}
checkIfInvalidModule(typeArguments) {
return (
typeArguments.type !== 'TypeParameterInstantiation' ||
typeArguments.params.length !== 1 ||
typeArguments.params[0].type !== 'GenericTypeAnnotation' ||
typeArguments.params[0].id.name !== 'Spec'
);
}
remapUnionTypeAnnotationMemberNames(membersTypes) {
const remapLiteral = item => {
return item.type
.replace('NumberLiteralTypeAnnotation', 'NumberTypeAnnotation')
.replace('StringLiteralTypeAnnotation', 'StringTypeAnnotation');
};
return [...new Set(membersTypes.map(remapLiteral))];
}
getStringLiteralUnionTypeAnnotationStringLiterals(membersTypes) {
return membersTypes.map(item => item.value);
}
parseFile(filename) {
const contents = fs.readFileSync(filename, 'utf8');
return this.parseString(contents, filename);
}
parseString(contents, filename) {
return buildSchema(
contents,
filename,
wrapComponentSchema,
buildComponentSchema,
buildModuleSchema,
Visitor,
this,
flowTranslateTypeAnnotation,
);
}
parseModuleFixture(filename) {
const contents = fs.readFileSync(filename, 'utf8');
return this.parseString(contents, 'path/NativeSampleTurboModule.js');
}
getAst(contents, filename) {
return parseFlowAndThrowErrors(contents, {
filename,
});
}
getFunctionTypeAnnotationParameters(functionTypeAnnotation) {
return functionTypeAnnotation.params;
}
getFunctionNameFromParameter(parameter) {
return parameter.name;
}
getParameterName(parameter) {
return parameter.name.name;
}
getParameterTypeAnnotation(parameter) {
return parameter.typeAnnotation;
}
getFunctionTypeAnnotationReturnType(functionTypeAnnotation) {
return functionTypeAnnotation.returnType;
}
parseEnumMembersType(typeAnnotation) {
const enumMembersType =
typeAnnotation.type === 'EnumStringBody'
? 'StringTypeAnnotation'
: typeAnnotation.type === 'EnumNumberBody'
? 'NumberTypeAnnotation'
: null;
if (!enumMembersType) {
throw new Error(
`Unknown enum type annotation type. Got: ${typeAnnotation.type}. Expected: EnumStringBody or EnumNumberBody.`,
);
}
return enumMembersType;
}
validateEnumMembersSupported(typeAnnotation, enumMembersType) {
if (!typeAnnotation.members || typeAnnotation.members.length === 0) {
// passing mixed members to flow would result in a flow error
// if the tool is launched ignoring that error, the enum would appear like not having enums
throw new Error(
'Enums should have at least one member and member values can not be mixed- they all must be either blank, number, or string values.',
);
}
typeAnnotation.members.forEach(member => {
if (
enumMembersType === 'StringTypeAnnotation' &&
(!member.init || typeof member.init.value === 'string')
) {
return;
}
if (
enumMembersType === 'NumberTypeAnnotation' &&
member.init &&
typeof member.init.value === 'number'
) {
return;
}
throw new Error(
'Enums can not be mixed- they all must be either blank, number, or string values.',
);
});
}
parseEnumMembers(typeAnnotation) {
return typeAnnotation.members.map(member => {
var _member$init, _member$init2;
const value =
typeof ((_member$init = member.init) === null || _member$init === void 0
? void 0
: _member$init.value) === 'number'
? {
type: 'NumberLiteralTypeAnnotation',
value: member.init.value,
}
: typeof ((_member$init2 = member.init) === null ||
_member$init2 === void 0
? void 0
: _member$init2.value) === 'string'
? {
type: 'StringLiteralTypeAnnotation',
value: member.init.value,
}
: {
type: 'StringLiteralTypeAnnotation',
value: member.id.name,
};
return {
name: member.id.name,
value: value,
};
});
}
isModuleInterface(node) {
return (
node.type === 'InterfaceDeclaration' &&
node.extends.length === 1 &&
node.extends[0].type === 'InterfaceExtends' &&
node.extends[0].id.name === 'TurboModule'
);
}
isGenericTypeAnnotation(type) {
return type === 'GenericTypeAnnotation';
}
extractAnnotatedElement(typeAnnotation, types) {
return types[typeAnnotation.typeParameters.params[0].id.name];
}
/**
* This FlowFixMe is supposed to refer to an InterfaceDeclaration or TypeAlias
* declaration type. Unfortunately, we don't have those types, because flow-parser
* generates them, and flow-parser is not type-safe. In the future, we should find
* a way to get these types from our flow parser library.
*
* TODO(T71778680): Flow type AST Nodes
*/
getTypes(ast) {
return ast.body.reduce((types, node) => {
if (
node.type === 'ExportNamedDeclaration' &&
node.exportKind === 'type'
) {
if (
node.declaration != null &&
(node.declaration.type === 'TypeAlias' ||
node.declaration.type === 'OpaqueType' ||
node.declaration.type === 'InterfaceDeclaration')
) {
types[node.declaration.id.name] = node.declaration;
}
} else if (
node.type === 'ExportNamedDeclaration' &&
node.exportKind === 'value' &&
node.declaration &&
node.declaration.type === 'EnumDeclaration'
) {
types[node.declaration.id.name] = node.declaration;
} else if (
node.type === 'TypeAlias' ||
node.type === 'OpaqueType' ||
node.type === 'InterfaceDeclaration' ||
node.type === 'EnumDeclaration'
) {
types[node.id.name] = node;
}
return types;
}, {});
}
callExpressionTypeParameters(callExpression) {
return callExpression.typeArguments || null;
}
computePartialProperties(
properties,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
) {
return properties.map(prop => {
return {
name: prop.key.name,
optional: true,
typeAnnotation: flowTranslateTypeAnnotation(
hasteModuleName,
prop.value,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
this,
),
};
});
}
functionTypeAnnotation(propertyValueType) {
return propertyValueType === 'FunctionTypeAnnotation';
}
getTypeArgumentParamsFromDeclaration(declaration) {
return declaration.typeArguments.params;
}
/**
* This FlowFixMe is supposed to refer to typeArgumentParams and
* funcArgumentParams of generated AST.
*/
getNativeComponentType(typeArgumentParams, funcArgumentParams) {
return {
propsTypeName: typeArgumentParams[0].id.name,
componentName: funcArgumentParams[0].value,
};
}
getAnnotatedElementProperties(annotatedElement) {
return annotatedElement.right.properties;
}
bodyProperties(typeAlias) {
return typeAlias.body.properties;
}
convertKeywordToTypeAnnotation(keyword) {
return keyword;
}
argumentForProp(prop) {
return prop.argument;
}
nameForArgument(prop) {
return prop.argument.id.name;
}
isOptionalProperty(property) {
return (
property.value.type === 'NullableTypeAnnotation' || property.optional
);
}
getGetSchemaInfoFN() {
return getSchemaInfo;
}
getTypeAnnotationFromProperty(property) {
return property.value.type === 'NullableTypeAnnotation'
? property.value.typeAnnotation
: property.value;
}
getGetTypeAnnotationFN() {
return getTypeAnnotation;
}
getResolvedTypeAnnotation(typeAnnotation, types, parser) {
invariant(
typeAnnotation != null,
'resolveTypeAnnotation(): typeAnnotation cannot be null',
);
let node = typeAnnotation;
let nullable = false;
let typeResolutionStatus = {
successful: false,
};
for (;;) {
if (node.type === 'NullableTypeAnnotation') {
nullable = true;
node = node.typeAnnotation;
continue;
}
if (node.type !== 'GenericTypeAnnotation') {
break;
}
const typeAnnotationName = this.getTypeAnnotationName(node);
const resolvedTypeAnnotation = types[typeAnnotationName];
if (resolvedTypeAnnotation == null) {
break;
}
const {typeAnnotation: typeAnnotationNode, typeResolutionStatus: status} =
handleGenericTypeAnnotation(node, resolvedTypeAnnotation, this);
typeResolutionStatus = status;
node = typeAnnotationNode;
}
return {
nullable: nullable,
typeAnnotation: node,
typeResolutionStatus,
};
}
getResolveTypeAnnotationFN() {
return (typeAnnotation, types, parser) =>
this.getResolvedTypeAnnotation(typeAnnotation, types, parser);
}
extendsForProp(prop, types, parser) {
const argument = this.argumentForProp(prop);
if (!argument) {
console.log('null', prop);
}
const name = parser.nameForArgument(prop);
if (types[name] != null) {
// This type is locally defined in the file
return null;
}
switch (name) {
case 'ViewProps':
return {
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
};
default: {
throw new Error(`Unable to handle prop spread: ${name}`);
}
}
}
removeKnownExtends(typeDefinition, types) {
return typeDefinition.filter(
prop =>
prop.type !== 'ObjectTypeSpreadProperty' ||
this.extendsForProp(prop, types, this) === null,
);
}
getExtendsProps(typeDefinition, types) {
return typeDefinition
.filter(prop => prop.type === 'ObjectTypeSpreadProperty')
.map(prop => this.extendsForProp(prop, types, this))
.filter(Boolean);
}
getProps(typeDefinition, types) {
const nonExtendsProps = this.removeKnownExtends(typeDefinition, types);
const props = flattenProperties(nonExtendsProps, types, this)
.map(property => buildPropSchema(property, types, this))
.filter(Boolean);
return {
props,
extendsProps: this.getExtendsProps(typeDefinition, types),
};
}
getProperties(typeName, types) {
const typeAlias = types[typeName];
try {
return typeAlias.right.typeParameters.params[0].properties;
} catch (e) {
throw new Error(
`Failed to find type definition for "${typeName}", please check that you have a valid codegen flow file`,
);
}
}
nextNodeForTypeAlias(typeAnnotation) {
if (typeAnnotation.type === 'OpaqueType') {
return typeAnnotation.impltype;
}
return typeAnnotation.right;
}
nextNodeForEnum(typeAnnotation) {
return typeAnnotation.body;
}
genericTypeAnnotationErrorMessage(typeAnnotation) {
return `A non GenericTypeAnnotation must be a type declaration ('${this.typeAlias}') or enum ('${this.enumDeclaration}'). Instead, got the unsupported ${typeAnnotation.type}.`;
}
extractTypeFromTypeAnnotation(typeAnnotation) {
return typeAnnotation.type === 'GenericTypeAnnotation'
? typeAnnotation.id.name
: typeAnnotation.type;
}
getObjectProperties(typeAnnotation) {
return typeAnnotation.properties;
}
getLiteralValue(option) {
return option.value;
}
getPaperTopLevelNameDeprecated(typeAnnotation) {
return typeAnnotation.typeParameters.params.length > 1
? typeAnnotation.typeParameters.params[1].value
: null;
}
}
module.exports = {
FlowParser,
};

View File

@@ -0,0 +1,586 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
ExtendsPropsShape,
NamedShape,
NativeModuleAliasMap,
NativeModuleEnumMap,
NativeModuleEnumMember,
NativeModuleEnumMemberType,
NativeModuleParamTypeAnnotation,
Nullable,
PropTypeAnnotation,
SchemaType,
UnionTypeAnnotationMemberType,
} from '../../CodegenSchema';
import type {ParserType} from '../errors';
import type {
GetSchemaInfoFN,
GetTypeAnnotationFN,
Parser,
ResolveTypeAnnotationFN,
} from '../parser';
import type {
ParserErrorCapturer,
PropAST,
TypeDeclarationMap,
TypeResolutionStatus,
} from '../utils';
const {
UnsupportedObjectPropertyTypeAnnotationParserError,
} = require('../errors');
const {
buildModuleSchema,
buildPropSchema,
buildSchema,
handleGenericTypeAnnotation,
} = require('../parsers-commons');
const {Visitor} = require('../parsers-primitives');
const {wrapComponentSchema} = require('../schema.js');
const {buildComponentSchema} = require('./components');
const {
flattenProperties,
getSchemaInfo,
getTypeAnnotation,
} = require('./components/componentsUtils');
const {flowTranslateTypeAnnotation} = require('./modules');
const {parseFlowAndThrowErrors} = require('./parseFlowAndThrowErrors');
const fs = require('fs');
const invariant = require('invariant');
type ExtendsForProp = null | {
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
};
class FlowParser implements Parser {
typeParameterInstantiation: string = 'TypeParameterInstantiation';
typeAlias: string = 'TypeAlias';
enumDeclaration: string = 'EnumDeclaration';
interfaceDeclaration: string = 'InterfaceDeclaration';
nullLiteralTypeAnnotation: string = 'NullLiteralTypeAnnotation';
undefinedLiteralTypeAnnotation: string = 'VoidLiteralTypeAnnotation';
isProperty(property: $FlowFixMe): boolean {
return property.type === 'ObjectTypeProperty';
}
getKeyName(property: $FlowFixMe, hasteModuleName: string): string {
if (!this.isProperty(property)) {
throw new UnsupportedObjectPropertyTypeAnnotationParserError(
hasteModuleName,
property,
property.type,
this.language(),
);
}
return property.key.name;
}
language(): ParserType {
return 'Flow';
}
getTypeAnnotationName(typeAnnotation: $FlowFixMe): string {
if (typeAnnotation?.id?.type === 'QualifiedTypeIdentifier') {
return typeAnnotation.id.id.name;
}
return typeAnnotation?.id?.name;
}
checkIfInvalidModule(typeArguments: $FlowFixMe): boolean {
return (
typeArguments.type !== 'TypeParameterInstantiation' ||
typeArguments.params.length !== 1 ||
typeArguments.params[0].type !== 'GenericTypeAnnotation' ||
typeArguments.params[0].id.name !== 'Spec'
);
}
remapUnionTypeAnnotationMemberNames(
membersTypes: $FlowFixMe[],
): UnionTypeAnnotationMemberType[] {
const remapLiteral = (item: $FlowFixMe) => {
return item.type
.replace('NumberLiteralTypeAnnotation', 'NumberTypeAnnotation')
.replace('StringLiteralTypeAnnotation', 'StringTypeAnnotation');
};
return [...new Set(membersTypes.map(remapLiteral))];
}
getStringLiteralUnionTypeAnnotationStringLiterals(
membersTypes: $FlowFixMe[],
): string[] {
return membersTypes.map((item: $FlowFixMe) => item.value);
}
parseFile(filename: string): SchemaType {
const contents = fs.readFileSync(filename, 'utf8');
return this.parseString(contents, filename);
}
parseString(contents: string, filename: ?string): SchemaType {
return buildSchema(
contents,
filename,
wrapComponentSchema,
buildComponentSchema,
buildModuleSchema,
Visitor,
this,
flowTranslateTypeAnnotation,
);
}
parseModuleFixture(filename: string): SchemaType {
const contents = fs.readFileSync(filename, 'utf8');
return this.parseString(contents, 'path/NativeSampleTurboModule.js');
}
getAst(contents: string, filename?: ?string): $FlowFixMe {
return parseFlowAndThrowErrors(contents, {filename});
}
getFunctionTypeAnnotationParameters(
functionTypeAnnotation: $FlowFixMe,
): $ReadOnlyArray<$FlowFixMe> {
return functionTypeAnnotation.params;
}
getFunctionNameFromParameter(
parameter: NamedShape<Nullable<NativeModuleParamTypeAnnotation>>,
): $FlowFixMe {
return parameter.name;
}
getParameterName(parameter: $FlowFixMe): string {
return parameter.name.name;
}
getParameterTypeAnnotation(parameter: $FlowFixMe): $FlowFixMe {
return parameter.typeAnnotation;
}
getFunctionTypeAnnotationReturnType(
functionTypeAnnotation: $FlowFixMe,
): $FlowFixMe {
return functionTypeAnnotation.returnType;
}
parseEnumMembersType(typeAnnotation: $FlowFixMe): NativeModuleEnumMemberType {
const enumMembersType: ?NativeModuleEnumMemberType =
typeAnnotation.type === 'EnumStringBody'
? 'StringTypeAnnotation'
: typeAnnotation.type === 'EnumNumberBody'
? 'NumberTypeAnnotation'
: null;
if (!enumMembersType) {
throw new Error(
`Unknown enum type annotation type. Got: ${typeAnnotation.type}. Expected: EnumStringBody or EnumNumberBody.`,
);
}
return enumMembersType;
}
validateEnumMembersSupported(
typeAnnotation: $FlowFixMe,
enumMembersType: NativeModuleEnumMemberType,
): void {
if (!typeAnnotation.members || typeAnnotation.members.length === 0) {
// passing mixed members to flow would result in a flow error
// if the tool is launched ignoring that error, the enum would appear like not having enums
throw new Error(
'Enums should have at least one member and member values can not be mixed- they all must be either blank, number, or string values.',
);
}
typeAnnotation.members.forEach(member => {
if (
enumMembersType === 'StringTypeAnnotation' &&
(!member.init || typeof member.init.value === 'string')
) {
return;
}
if (
enumMembersType === 'NumberTypeAnnotation' &&
member.init &&
typeof member.init.value === 'number'
) {
return;
}
throw new Error(
'Enums can not be mixed- they all must be either blank, number, or string values.',
);
});
}
parseEnumMembers(
typeAnnotation: $FlowFixMe,
): $ReadOnlyArray<NativeModuleEnumMember> {
return typeAnnotation.members.map(member => {
const value =
typeof member.init?.value === 'number'
? {
type: 'NumberLiteralTypeAnnotation',
value: member.init.value,
}
: typeof member.init?.value === 'string'
? {
type: 'StringLiteralTypeAnnotation',
value: member.init.value,
}
: {
type: 'StringLiteralTypeAnnotation',
value: member.id.name,
};
return {
name: member.id.name,
value: value,
};
});
}
isModuleInterface(node: $FlowFixMe): boolean {
return (
node.type === 'InterfaceDeclaration' &&
node.extends.length === 1 &&
node.extends[0].type === 'InterfaceExtends' &&
node.extends[0].id.name === 'TurboModule'
);
}
isGenericTypeAnnotation(type: $FlowFixMe): boolean {
return type === 'GenericTypeAnnotation';
}
extractAnnotatedElement(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
): $FlowFixMe {
return types[typeAnnotation.typeParameters.params[0].id.name];
}
/**
* This FlowFixMe is supposed to refer to an InterfaceDeclaration or TypeAlias
* declaration type. Unfortunately, we don't have those types, because flow-parser
* generates them, and flow-parser is not type-safe. In the future, we should find
* a way to get these types from our flow parser library.
*
* TODO(T71778680): Flow type AST Nodes
*/
getTypes(ast: $FlowFixMe): TypeDeclarationMap {
return ast.body.reduce((types, node) => {
if (
node.type === 'ExportNamedDeclaration' &&
node.exportKind === 'type'
) {
if (
node.declaration != null &&
(node.declaration.type === 'TypeAlias' ||
node.declaration.type === 'OpaqueType' ||
node.declaration.type === 'InterfaceDeclaration')
) {
types[node.declaration.id.name] = node.declaration;
}
} else if (
node.type === 'ExportNamedDeclaration' &&
node.exportKind === 'value' &&
node.declaration &&
node.declaration.type === 'EnumDeclaration'
) {
types[node.declaration.id.name] = node.declaration;
} else if (
node.type === 'TypeAlias' ||
node.type === 'OpaqueType' ||
node.type === 'InterfaceDeclaration' ||
node.type === 'EnumDeclaration'
) {
types[node.id.name] = node;
}
return types;
}, {});
}
callExpressionTypeParameters(callExpression: $FlowFixMe): $FlowFixMe | null {
return callExpression.typeArguments || null;
}
computePartialProperties(
properties: Array<$FlowFixMe>,
hasteModuleName: string,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
): Array<$FlowFixMe> {
return properties.map(prop => {
return {
name: prop.key.name,
optional: true,
typeAnnotation: flowTranslateTypeAnnotation(
hasteModuleName,
prop.value,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
this,
),
};
});
}
functionTypeAnnotation(propertyValueType: string): boolean {
return propertyValueType === 'FunctionTypeAnnotation';
}
getTypeArgumentParamsFromDeclaration(declaration: $FlowFixMe): $FlowFixMe {
return declaration.typeArguments.params;
}
/**
* This FlowFixMe is supposed to refer to typeArgumentParams and
* funcArgumentParams of generated AST.
*/
getNativeComponentType(
typeArgumentParams: $FlowFixMe,
funcArgumentParams: $FlowFixMe,
): {[string]: string} {
return {
propsTypeName: typeArgumentParams[0].id.name,
componentName: funcArgumentParams[0].value,
};
}
getAnnotatedElementProperties(annotatedElement: $FlowFixMe): $FlowFixMe {
return annotatedElement.right.properties;
}
bodyProperties(typeAlias: $FlowFixMe): $ReadOnlyArray<$FlowFixMe> {
return typeAlias.body.properties;
}
convertKeywordToTypeAnnotation(keyword: string): string {
return keyword;
}
argumentForProp(prop: PropAST): $FlowFixMe {
return prop.argument;
}
nameForArgument(prop: PropAST): $FlowFixMe {
return prop.argument.id.name;
}
isOptionalProperty(property: $FlowFixMe): boolean {
return (
property.value.type === 'NullableTypeAnnotation' || property.optional
);
}
getGetSchemaInfoFN(): GetSchemaInfoFN {
return getSchemaInfo;
}
getTypeAnnotationFromProperty(property: PropAST): $FlowFixMe {
return property.value.type === 'NullableTypeAnnotation'
? property.value.typeAnnotation
: property.value;
}
getGetTypeAnnotationFN(): GetTypeAnnotationFN {
return getTypeAnnotation;
}
getResolvedTypeAnnotation(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
parser: Parser,
): {
nullable: boolean,
typeAnnotation: $FlowFixMe,
typeResolutionStatus: TypeResolutionStatus,
} {
invariant(
typeAnnotation != null,
'resolveTypeAnnotation(): typeAnnotation cannot be null',
);
let node = typeAnnotation;
let nullable = false;
let typeResolutionStatus: TypeResolutionStatus = {
successful: false,
};
for (;;) {
if (node.type === 'NullableTypeAnnotation') {
nullable = true;
node = node.typeAnnotation;
continue;
}
if (node.type !== 'GenericTypeAnnotation') {
break;
}
const typeAnnotationName = this.getTypeAnnotationName(node);
const resolvedTypeAnnotation = types[typeAnnotationName];
if (resolvedTypeAnnotation == null) {
break;
}
const {typeAnnotation: typeAnnotationNode, typeResolutionStatus: status} =
handleGenericTypeAnnotation(node, resolvedTypeAnnotation, this);
typeResolutionStatus = status;
node = typeAnnotationNode;
}
return {
nullable: nullable,
typeAnnotation: node,
typeResolutionStatus,
};
}
getResolveTypeAnnotationFN(): ResolveTypeAnnotationFN {
return (typeAnnotation, types, parser) =>
this.getResolvedTypeAnnotation(typeAnnotation, types, parser);
}
extendsForProp(
prop: PropAST,
types: TypeDeclarationMap,
parser: Parser,
): ExtendsForProp {
const argument = this.argumentForProp(prop);
if (!argument) {
console.log('null', prop);
}
const name = parser.nameForArgument(prop);
if (types[name] != null) {
// This type is locally defined in the file
return null;
}
switch (name) {
case 'ViewProps':
return {
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
};
default: {
throw new Error(`Unable to handle prop spread: ${name}`);
}
}
}
removeKnownExtends(
typeDefinition: $ReadOnlyArray<PropAST>,
types: TypeDeclarationMap,
): $ReadOnlyArray<PropAST> {
return typeDefinition.filter(
prop =>
prop.type !== 'ObjectTypeSpreadProperty' ||
this.extendsForProp(prop, types, this) === null,
);
}
getExtendsProps(
typeDefinition: $ReadOnlyArray<PropAST>,
types: TypeDeclarationMap,
): $ReadOnlyArray<ExtendsPropsShape> {
return typeDefinition
.filter(prop => prop.type === 'ObjectTypeSpreadProperty')
.map(prop => this.extendsForProp(prop, types, this))
.filter(Boolean);
}
getProps(
typeDefinition: $ReadOnlyArray<PropAST>,
types: TypeDeclarationMap,
): {
props: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
extendsProps: $ReadOnlyArray<ExtendsPropsShape>,
} {
const nonExtendsProps = this.removeKnownExtends(typeDefinition, types);
const props = flattenProperties(nonExtendsProps, types, this)
.map(property => buildPropSchema(property, types, this))
.filter(Boolean);
return {
props,
extendsProps: this.getExtendsProps(typeDefinition, types),
};
}
getProperties(typeName: string, types: TypeDeclarationMap): $FlowFixMe {
const typeAlias = types[typeName];
try {
return typeAlias.right.typeParameters.params[0].properties;
} catch (e) {
throw new Error(
`Failed to find type definition for "${typeName}", please check that you have a valid codegen flow file`,
);
}
}
nextNodeForTypeAlias(typeAnnotation: $FlowFixMe): $FlowFixMe {
if (typeAnnotation.type === 'OpaqueType') {
return typeAnnotation.impltype;
}
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}.`;
}
extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string {
return typeAnnotation.type === 'GenericTypeAnnotation'
? typeAnnotation.id.name
: typeAnnotation.type;
}
getObjectProperties(typeAnnotation: $FlowFixMe): $FlowFixMe {
return typeAnnotation.properties;
}
getLiteralValue(option: $FlowFixMe): $FlowFixMe {
return option.value;
}
getPaperTopLevelNameDeprecated(typeAnnotation: $FlowFixMe): $FlowFixMe {
return typeAnnotation.typeParameters.params.length > 1
? typeAnnotation.typeParameters.params[1].value
: null;
}
}
module.exports = {
FlowParser,
};

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function getValueFromTypes(value, types) {
if (value.type === 'GenericTypeAnnotation' && types[value.id.name]) {
return getValueFromTypes(types[value.id.name].right, types);
}
return value;
}
module.exports = {
getValueFromTypes,
};

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {ASTNode, TypeDeclarationMap} from '../utils';
function getValueFromTypes(value: ASTNode, types: TypeDeclarationMap): ASTNode {
if (value.type === 'GenericTypeAnnotation' && types[value.id.name]) {
return getValueFromTypes(types[value.id.name].right, types);
}
return value;
}
module.exports = {
getValueFromTypes,
};

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { SchemaType } from '../CodegenSchema';
import type { ParserType } from './errors';
// useful members only for downstream
export interface Parser {
language(): ParserType;
parseFile(filename: string): SchemaType;
parseString(contents: string, filename?: string): SchemaType;
parseModuleFixture(filename: string): SchemaType;
}

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';

View File

@@ -0,0 +1,444 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
ExtendsPropsShape,
NamedShape,
NativeModuleAliasMap,
NativeModuleEnumMap,
NativeModuleEnumMember,
NativeModuleEnumMemberType,
NativeModuleParamTypeAnnotation,
Nullable,
PropTypeAnnotation,
SchemaType,
UnionTypeAnnotationMemberType,
} from '../CodegenSchema';
import type {ParserType} from './errors';
import type {
ASTNode,
ParserErrorCapturer,
PropAST,
TypeDeclarationMap,
TypeResolutionStatus,
} from './utils';
export type GetTypeAnnotationFN = (
name: string,
annotation: $FlowFixMe | ASTNode,
defaultValue: $FlowFixMe | void,
withNullDefault: boolean,
types: TypeDeclarationMap,
parser: Parser,
buildSchema: (
property: PropAST,
types: TypeDeclarationMap,
parser: Parser,
) => $FlowFixMe,
) => $FlowFixMe;
export type SchemaInfo = {
name: string,
optional: boolean,
typeAnnotation: $FlowFixMe,
defaultValue: $FlowFixMe,
withNullDefault: boolean,
};
export type GetSchemaInfoFN = (
property: PropAST,
types: TypeDeclarationMap,
parser: Parser,
) => ?SchemaInfo;
export type BuildSchemaFN<T> = (
property: PropAST,
types: TypeDeclarationMap,
parser: Parser,
) => ?NamedShape<T>;
export type ResolveTypeAnnotationFN = (
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
parser: Parser,
) => {
nullable: boolean,
typeAnnotation: $FlowFixMe,
typeResolutionStatus: TypeResolutionStatus,
};
/**
* This is the main interface for Parsers of various languages.
* It exposes all the methods that contain language-specific logic.
*/
export interface Parser {
/**
* This is the TypeParameterInstantiation value
*/
typeParameterInstantiation: string;
/**
* TypeAlias property of the Parser
*/
typeAlias: string;
/**
* enumDeclaration Property of the Parser
*/
enumDeclaration: string;
/**
* InterfaceDeclaration property of the Parser
*/
interfaceDeclaration: string;
/**
* This is the NullLiteralTypeAnnotation value
*/
nullLiteralTypeAnnotation: string;
/**
* UndefinedLiteralTypeAnnotation property of the Parser
*/
undefinedLiteralTypeAnnotation: string;
/**
* Given a declaration, it returns true if it is a property
*/
isProperty(property: $FlowFixMe): boolean;
/**
* Given a property declaration, it returns the key name.
* @parameter property: an object containing a property declaration.
* @parameter hasteModuleName: a string with the native module name.
* @returns: the key name.
* @throws if property does not contain a property declaration.
*/
getKeyName(property: $FlowFixMe, hasteModuleName: string): string;
/**
* @returns: the Parser language.
*/
language(): ParserType;
/**
* Given a type annotation, it returns the type name.
* @parameter typeAnnotation: the annotation for a type in the AST.
* @returns: the name of the type.
*/
getTypeAnnotationName(typeAnnotation: $FlowFixMe): string;
/**
* Given a type arguments, it returns a boolean specifying if the Module is Invalid.
* @parameter typeArguments: the type arguments.
* @returns: a boolean specifying if the Module is Invalid.
*/
checkIfInvalidModule(typeArguments: $FlowFixMe): boolean;
/**
* Given a union annotation members types, it returns an array of remaped members names without duplicates.
* @parameter membersTypes: union annotation members types
* @returns: an array of remaped members names without duplicates.
*/
remapUnionTypeAnnotationMemberNames(
types: $FlowFixMe,
): UnionTypeAnnotationMemberType[];
/**
* Given a union annotation members types, it returns an array of string literals.
* @parameter membersTypes: union annotation members types
* @returns: an array of string literals.
*/
getStringLiteralUnionTypeAnnotationStringLiterals(
types: $FlowFixMe,
): string[];
/**
* Given the name of a file, it returns a Schema.
* @parameter filename: the name of the file.
* @returns: the Schema of the file.
*/
parseFile(filename: string): SchemaType;
/**
* Given the content of a file, it returns a Schema.
* @parameter contents: the content of the file.
* @parameter filename: the name of the file.
* @returns: the Schema of the file.
*/
parseString(contents: string, filename: ?string): SchemaType;
/**
* Given the name of a file, it returns a Schema.
* @parameter filename: the name of the file.
* @returns: the Schema of the file.
*/
parseModuleFixture(filename: string): SchemaType;
/**
* Given the content of a file, it returns an AST.
* @parameter contents: the content of the file.
* @parameter filename: the name of the file, if available.
* @throws if there is a syntax error.
* @returns: the AST of the file.
*/
getAst(contents: string, filename?: ?string): $FlowFixMe;
/**
* Given a FunctionTypeAnnotation, it returns an array of its parameters.
* @parameter functionTypeAnnotation: a FunctionTypeAnnotation
* @returns: the parameters of the FunctionTypeAnnotation.
*/
getFunctionTypeAnnotationParameters(
functionTypeAnnotation: $FlowFixMe,
): $ReadOnlyArray<$FlowFixMe>;
/**
* Given a parameter, it returns the function name of the parameter.
* @parameter parameter: a parameter of a FunctionTypeAnnotation.
* @returns: the function name of the parameter.
*/
getFunctionNameFromParameter(
parameter: NamedShape<Nullable<NativeModuleParamTypeAnnotation>>,
): $FlowFixMe;
/**
* Given a parameter, it returns its name.
* @parameter parameter: a parameter of a FunctionTypeAnnotation.
* @returns: the name of the parameter.
*/
getParameterName(parameter: $FlowFixMe): string;
/**
* Given a parameter, it returns its typeAnnotation.
* @parameter parameter: a parameter of a FunctionTypeAnnotation.
* @returns: the typeAnnotation of the parameter.
*/
getParameterTypeAnnotation(param: $FlowFixMe): $FlowFixMe;
/**
* Given a FunctionTypeAnnotation, it returns its returnType.
* @parameter functionTypeAnnotation: a FunctionTypeAnnotation
* @returns: the returnType of the FunctionTypeAnnotation.
*/
getFunctionTypeAnnotationReturnType(
functionTypeAnnotation: $FlowFixMe,
): $FlowFixMe;
/**
* Calculates an enum's members type
*/
parseEnumMembersType(typeAnnotation: $FlowFixMe): NativeModuleEnumMemberType;
/**
* Throws if enum mebers are not supported
*/
validateEnumMembersSupported(
typeAnnotation: $FlowFixMe,
enumMembersType: NativeModuleEnumMemberType,
): void;
/**
* Calculates enum's members
*/
parseEnumMembers(
typeAnnotation: $FlowFixMe,
): $ReadOnlyArray<NativeModuleEnumMember>;
/**
* Given a node, it returns true if it is a module interface
*/
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.
* @parameter types: a map of type declarations.
* @returns: the annotated element.
*/
extractAnnotatedElement(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
): $FlowFixMe;
/**
* Given the AST, returns the TypeDeclarationMap
*/
getTypes(ast: $FlowFixMe): TypeDeclarationMap;
/**
* Given a callExpression, it returns the typeParameters of the callExpression.
* @parameter callExpression: the callExpression.
* @returns: the typeParameters of the callExpression or null if it does not exist.
*/
callExpressionTypeParameters(callExpression: $FlowFixMe): $FlowFixMe | null;
/**
* Given an array of properties from a Partial type, it returns an array of remaped properties.
* @parameter properties: properties from a Partial types.
* @parameter hasteModuleName: a string with the native module name.
* @parameter types: a map of type declarations.
* @parameter aliasMap: a map of type aliases.
* @parameter enumMap: a map of type enums.
* @parameter tryParse: a parser error capturer.
* @parameter cxxOnly: a boolean specifying if the module is Cxx only.
* @returns: an array of remaped properties
*/
computePartialProperties(
properties: Array<$FlowFixMe>,
hasteModuleName: string,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
): Array<$FlowFixMe>;
/**
* Given a propertyValueType, it returns a boolean specifying if the property is a function type annotation.
* @parameter propertyValueType: the propertyValueType.
* @returns: a boolean specifying if the property is a function type annotation.
*/
functionTypeAnnotation(propertyValueType: string): boolean;
/**
* Given a declaration, it returns the typeArgumentParams of the declaration.
* @parameter declaration: the declaration.
* @returns: the typeArgumentParams of the declaration.
*/
getTypeArgumentParamsFromDeclaration(declaration: $FlowFixMe): $FlowFixMe;
/**
* Given a typeArgumentParams and funcArgumentParams it returns a native component type.
* @parameter typeArgumentParams: the typeArgumentParams.
* @parameter funcArgumentParams: the funcArgumentParams.
* @returns: a native component type.
*/
getNativeComponentType(
typeArgumentParams: $FlowFixMe,
funcArgumentParams: $FlowFixMe,
): {[string]: string};
/**
* Given a annotatedElement, it returns the properties of annotated element.
* @parameter annotatedElement: the annotated element.
* @returns: the properties of annotated element.
*/
getAnnotatedElementProperties(annotatedElement: $FlowFixMe): $FlowFixMe;
/**
* Given a typeAlias, it returns an array of properties.
* @parameter typeAlias: the type alias.
* @returns: an array of properties.
*/
bodyProperties(typeAlias: $FlowFixMe): $ReadOnlyArray<$FlowFixMe>;
/**
* Given a keyword convert it to TypeAnnotation.
* @parameter keyword
* @returns: converted TypeAnnotation to Keywords
*/
convertKeywordToTypeAnnotation(keyword: string): string;
/**
* Given a prop return its arguments.
* @parameter prop
* @returns: Arguments of the prop
*/
argumentForProp(prop: PropAST): $FlowFixMe;
/**
* Given a prop return its name.
* @parameter prop
* @returns: name property
*/
nameForArgument(prop: PropAST): $FlowFixMe;
/**
* Given a property return if it is optional.
* @parameter property
* @returns: a boolean specifying if the Property is optional
*/
isOptionalProperty(property: $FlowFixMe): boolean;
getGetTypeAnnotationFN(): GetTypeAnnotationFN;
getGetSchemaInfoFN(): GetSchemaInfoFN;
/**
* Given a property return the type annotation.
* @parameter property
* @returns: the annotation for a type in the AST.
*/
getTypeAnnotationFromProperty(property: PropAST): $FlowFixMe;
getResolvedTypeAnnotation(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
parser: Parser,
): {
nullable: boolean,
typeAnnotation: $FlowFixMe,
typeResolutionStatus: TypeResolutionStatus,
};
getResolveTypeAnnotationFN(): ResolveTypeAnnotationFN;
getProps(
typeDefinition: $ReadOnlyArray<PropAST>,
types: TypeDeclarationMap,
): {
props: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
extendsProps: $ReadOnlyArray<ExtendsPropsShape>,
};
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;
/**
* Given a type annotation, it extracts the type.
* @parameter typeAnnotation: the annotation for a type in the AST.
* @returns: the extracted type.
*/
extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string;
/**
* Given a typeAnnotation return the properties of an object.
* @parameter property
* @returns: the properties of an object represented by a type annotation.
*/
getObjectProperties(typeAnnotation: $FlowFixMe): $FlowFixMe;
/**
* Given a option return the literal value.
* @parameter option
* @returns: the literal value of an union represented.
*/
getLiteralValue(option: $FlowFixMe): $FlowFixMe;
/**
* Given a type annotation, it returns top level name in the AST if it exists else returns null.
* @parameter typeAnnotation: the annotation for a type in the AST.
* @returns: the top level name properties in the AST if it exists else null.
*/
getPaperTopLevelNameDeprecated(typeAnnotation: $FlowFixMe): $FlowFixMe;
}

View File

@@ -0,0 +1,434 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function _defineProperty(e, r, t) {
return (
(r = _toPropertyKey(r)) in e
? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0,
})
: (e[r] = t),
e
);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, 'string');
return 'symbol' == typeof i ? i : i + '';
}
function _toPrimitive(t, r) {
if ('object' != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || 'default');
if ('object' != typeof i) return i;
throw new TypeError('@@toPrimitive must return a primitive value.');
}
return ('string' === r ? String : Number)(t);
}
import invariant from 'invariant';
const {
UnsupportedObjectPropertyTypeAnnotationParserError,
} = require('./errors');
const {parseFlowAndThrowErrors} = require('./flow/parseFlowAndThrowErrors');
const {buildPropSchema} = require('./parsers-commons');
const {flattenProperties} = require('./typescript/components/componentsUtils');
const schemaMock = {
modules: {
StringPropNativeComponentView: {
type: 'Component',
components: {
StringPropNativeComponentView: {
extendsProps: [],
events: [],
props: [],
commands: [],
},
},
},
},
};
export class MockedParser {
constructor() {
_defineProperty(
this,
'typeParameterInstantiation',
'TypeParameterInstantiation',
);
_defineProperty(this, 'typeAlias', 'TypeAlias');
_defineProperty(this, 'enumDeclaration', 'EnumDeclaration');
_defineProperty(this, 'interfaceDeclaration', 'InterfaceDeclaration');
_defineProperty(
this,
'nullLiteralTypeAnnotation',
'NullLiteralTypeAnnotation',
);
_defineProperty(
this,
'undefinedLiteralTypeAnnotation',
'VoidLiteralTypeAnnotation',
);
}
isProperty(property) {
return property.type === 'ObjectTypeProperty';
}
getKeyName(property, hasteModuleName) {
if (!this.isProperty(property)) {
throw new UnsupportedObjectPropertyTypeAnnotationParserError(
hasteModuleName,
property,
property.type,
this.language(),
);
}
return property.key.name;
}
language() {
return 'Flow';
}
getTypeAnnotationName(typeAnnotation) {
var _typeAnnotation$id;
return typeAnnotation === null ||
typeAnnotation === void 0 ||
(_typeAnnotation$id = typeAnnotation.id) === null ||
_typeAnnotation$id === void 0
? void 0
: _typeAnnotation$id.name;
}
checkIfInvalidModule(typeArguments) {
return false;
}
remapUnionTypeAnnotationMemberNames(membersTypes) {
return [];
}
getStringLiteralUnionTypeAnnotationStringLiterals(membersTypes) {
return [];
}
parseFile(filename) {
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return schemaMock;
}
parseString(contents, filename) {
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return schemaMock;
}
parseModuleFixture(filename) {
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return schemaMock;
}
getAst(contents, filename) {
return parseFlowAndThrowErrors(contents, {
filename,
});
}
getFunctionTypeAnnotationParameters(functionTypeAnnotation) {
return functionTypeAnnotation.params;
}
getFunctionNameFromParameter(parameter) {
return parameter.name;
}
getParameterName(parameter) {
return parameter.name.name;
}
getParameterTypeAnnotation(parameter) {
return parameter.typeAnnotation;
}
getFunctionTypeAnnotationReturnType(functionTypeAnnotation) {
return functionTypeAnnotation.returnType;
}
parseEnumMembersType(typeAnnotation) {
return typeAnnotation.type;
}
validateEnumMembersSupported(typeAnnotation, enumMembersType) {
return;
}
parseEnumMembers(typeAnnotation) {
return typeAnnotation.type === 'StringTypeAnnotation'
? [
{
name: 'Hello',
value: {
type: 'StringLiteralTypeAnnotation',
value: 'hello',
},
},
{
name: 'Goodbye',
value: {
type: 'StringLiteralTypeAnnotation',
value: 'goodbye',
},
},
]
: [
{
name: 'On',
value: {
type: 'NumberLiteralTypeAnnotation',
value: 1,
},
},
{
name: 'Off',
value: {
type: 'NumberLiteralTypeAnnotation',
value: 0,
},
},
];
}
isModuleInterface(node) {
return (
node.type === 'InterfaceDeclaration' &&
node.extends.length === 1 &&
node.extends[0].type === 'InterfaceExtends' &&
node.extends[0].id.name === 'TurboModule'
);
}
isGenericTypeAnnotation(type) {
return true;
}
extractAnnotatedElement(typeAnnotation, types) {
return types[typeAnnotation.typeParameters.params[0].id.name];
}
getTypes(ast) {
return {};
}
callExpressionTypeParameters(callExpression) {
return callExpression.typeArguments || null;
}
computePartialProperties(
properties,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
) {
return [
{
name: 'a',
optional: true,
typeAnnotation: {
type: 'StringTypeAnnotation',
},
},
{
name: 'b',
optional: true,
typeAnnotation: {
type: 'BooleanTypeAnnotation',
},
},
];
}
functionTypeAnnotation(propertyValueType) {
return propertyValueType === 'FunctionTypeAnnotation';
}
getTypeArgumentParamsFromDeclaration(declaration) {
return declaration.typeArguments.params;
}
getNativeComponentType(typeArgumentParams, funcArgumentParams) {
return {
propsTypeName: typeArgumentParams[0].id.name,
componentName: funcArgumentParams[0].value,
};
}
getAnnotatedElementProperties(annotatedElement) {
return annotatedElement.right.properties;
}
bodyProperties(typeAlias) {
return typeAlias.body.properties;
}
convertKeywordToTypeAnnotation(keyword) {
return keyword;
}
argumentForProp(prop) {
return prop.expression;
}
nameForArgument(prop) {
return prop.expression.name;
}
isOptionalProperty(property) {
return property.optional || false;
}
getTypeAnnotationFromProperty(property) {
return property.typeAnnotation.typeAnnotation;
}
getGetTypeAnnotationFN() {
return () => {
return {};
};
}
getGetSchemaInfoFN() {
return () => {
return {
name: 'MockedSchema',
optional: false,
typeAnnotation: 'BooleanTypeAnnotation',
defaultValue: false,
withNullDefault: false,
};
};
}
getResolveTypeAnnotationFN() {
return () => {
return {
nullable: false,
typeAnnotation: null,
typeResolutionStatus: {
successful: false,
},
};
};
}
getResolvedTypeAnnotation(typeAnnotation, types) {
invariant(
typeAnnotation != null,
'resolveTypeAnnotation(): typeAnnotation cannot be null',
);
let node = typeAnnotation;
let nullable = false;
let typeResolutionStatus = {
successful: false,
};
for (;;) {
if (node.type === 'NullableTypeAnnotation') {
nullable = true;
node = node.typeAnnotation;
continue;
}
if (node.type !== 'GenericTypeAnnotation') {
break;
}
const resolvedTypeAnnotation = types[node.id.name];
if (resolvedTypeAnnotation == null) {
break;
}
switch (resolvedTypeAnnotation.type) {
case 'TypeAlias': {
typeResolutionStatus = {
successful: true,
type: 'alias',
name: node.id.name,
};
node = resolvedTypeAnnotation.right;
break;
}
case '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 ('TypeAlias') or enum ('EnumDeclaration'). Instead, got the unsupported ${resolvedTypeAnnotation.type}.`,
);
}
}
}
return {
nullable: nullable,
typeAnnotation: node,
typeResolutionStatus,
};
}
getExtendsProps(typeDefinition, types) {
return typeDefinition
.filter(prop => prop.type === 'ObjectTypeSpreadProperty')
.map(prop => this.extendsForProp(prop, types, this))
.filter(Boolean);
}
extendsForProp(prop, types, parser) {
const argument = this.argumentForProp(prop);
if (!argument) {
console.log('null', prop);
}
const name = parser.nameForArgument(prop);
if (types[name] != null) {
// This type is locally defined in the file
return null;
}
switch (name) {
case 'ViewProps':
return {
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
};
default: {
throw new Error(`Unable to handle prop spread: ${name}`);
}
}
}
removeKnownExtends(typeDefinition, types) {
return typeDefinition.filter(
prop =>
prop.type !== 'ObjectTypeSpreadProperty' ||
this.extendsForProp(prop, types, this) === null,
);
}
getProps(typeDefinition, types) {
const nonExtendsProps = this.removeKnownExtends(typeDefinition, types);
const props = flattenProperties(nonExtendsProps, types, this)
.map(property => buildPropSchema(property, types, this))
.filter(Boolean);
return {
props,
extendsProps: this.getExtendsProps(typeDefinition, types),
};
}
getProperties(typeName, types) {
const typeAlias = types[typeName];
try {
return typeAlias.right.typeParameters.params[0].properties;
} catch (e) {
throw new Error(
`Failed to find type definition for "${typeName}", please check that you have a valid codegen flow file`,
);
}
}
nextNodeForTypeAlias(typeAnnotation) {
return typeAnnotation.right;
}
nextNodeForEnum(typeAnnotation) {
return typeAnnotation.body;
}
genericTypeAnnotationErrorMessage(typeAnnotation) {
return `A non GenericTypeAnnotation must be a type declaration ('${this.typeAlias}') or enum ('${this.enumDeclaration}'). Instead, got the unsupported ${typeAnnotation.type}.`;
}
extractTypeFromTypeAnnotation(typeAnnotation) {
return typeAnnotation.type === 'GenericTypeAnnotation'
? typeAnnotation.id.name
: typeAnnotation.type;
}
getObjectProperties(typeAnnotation) {
return typeAnnotation.properties;
}
getLiteralValue(option) {
return option.value;
}
getPaperTopLevelNameDeprecated(typeAnnotation) {
return typeAnnotation.typeParameters.params.length > 1
? typeAnnotation.typeParameters.params[1].value
: null;
}
}

View File

@@ -0,0 +1,518 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
ExtendsPropsShape,
NamedShape,
NativeModuleAliasMap,
NativeModuleEnumMap,
NativeModuleEnumMember,
NativeModuleEnumMemberType,
NativeModuleParamTypeAnnotation,
Nullable,
PropTypeAnnotation,
SchemaType,
UnionTypeAnnotationMemberType,
} from '../CodegenSchema';
import type {ParserType} from './errors';
import type {
GetSchemaInfoFN,
GetTypeAnnotationFN,
Parser,
ResolveTypeAnnotationFN,
} from './parser';
import type {
ParserErrorCapturer,
PropAST,
TypeDeclarationMap,
TypeResolutionStatus,
} from './utils';
import invariant from 'invariant';
const {
UnsupportedObjectPropertyTypeAnnotationParserError,
} = require('./errors');
const {parseFlowAndThrowErrors} = require('./flow/parseFlowAndThrowErrors');
const {buildPropSchema} = require('./parsers-commons');
const {flattenProperties} = require('./typescript/components/componentsUtils');
type ExtendsForProp = null | {
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
};
const schemaMock = {
modules: {
StringPropNativeComponentView: {
type: 'Component',
components: {
StringPropNativeComponentView: {
extendsProps: [],
events: [],
props: [],
commands: [],
},
},
},
},
};
export class MockedParser implements Parser {
typeParameterInstantiation: string = 'TypeParameterInstantiation';
typeAlias: string = 'TypeAlias';
enumDeclaration: string = 'EnumDeclaration';
interfaceDeclaration: string = 'InterfaceDeclaration';
nullLiteralTypeAnnotation: string = 'NullLiteralTypeAnnotation';
undefinedLiteralTypeAnnotation: string = 'VoidLiteralTypeAnnotation';
isProperty(property: $FlowFixMe): boolean {
return property.type === 'ObjectTypeProperty';
}
getKeyName(property: $FlowFixMe, hasteModuleName: string): string {
if (!this.isProperty(property)) {
throw new UnsupportedObjectPropertyTypeAnnotationParserError(
hasteModuleName,
property,
property.type,
this.language(),
);
}
return property.key.name;
}
language(): ParserType {
return 'Flow';
}
getTypeAnnotationName(typeAnnotation: $FlowFixMe): string {
return typeAnnotation?.id?.name;
}
checkIfInvalidModule(typeArguments: $FlowFixMe): boolean {
return false;
}
remapUnionTypeAnnotationMemberNames(
membersTypes: $FlowFixMe[],
): UnionTypeAnnotationMemberType[] {
return [];
}
getStringLiteralUnionTypeAnnotationStringLiterals(
membersTypes: $FlowFixMe[],
): string[] {
return [];
}
parseFile(filename: string): SchemaType {
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return schemaMock;
}
parseString(contents: string, filename: ?string): SchemaType {
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return schemaMock;
}
parseModuleFixture(filename: string): SchemaType {
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return schemaMock;
}
getAst(contents: string, filename?: ?string): $FlowFixMe {
return parseFlowAndThrowErrors(contents, {filename});
}
getFunctionTypeAnnotationParameters(
functionTypeAnnotation: $FlowFixMe,
): $ReadOnlyArray<$FlowFixMe> {
return functionTypeAnnotation.params;
}
getFunctionNameFromParameter(
parameter: NamedShape<Nullable<NativeModuleParamTypeAnnotation>>,
): $FlowFixMe {
return parameter.name;
}
getParameterName(parameter: $FlowFixMe): string {
return parameter.name.name;
}
getParameterTypeAnnotation(parameter: $FlowFixMe): $FlowFixMe {
return parameter.typeAnnotation;
}
getFunctionTypeAnnotationReturnType(
functionTypeAnnotation: $FlowFixMe,
): $FlowFixMe {
return functionTypeAnnotation.returnType;
}
parseEnumMembersType(typeAnnotation: $FlowFixMe): NativeModuleEnumMemberType {
return typeAnnotation.type;
}
validateEnumMembersSupported(
typeAnnotation: $FlowFixMe,
enumMembersType: NativeModuleEnumMemberType,
): void {
return;
}
parseEnumMembers(
typeAnnotation: $FlowFixMe,
): $ReadOnlyArray<NativeModuleEnumMember> {
return typeAnnotation.type === 'StringTypeAnnotation'
? [
{
name: 'Hello',
value: {
type: 'StringLiteralTypeAnnotation',
value: 'hello',
},
},
{
name: 'Goodbye',
value: {
type: 'StringLiteralTypeAnnotation',
value: 'goodbye',
},
},
]
: [
{
name: 'On',
value: {
type: 'NumberLiteralTypeAnnotation',
value: 1,
},
},
{
name: 'Off',
value: {
type: 'NumberLiteralTypeAnnotation',
value: 0,
},
},
];
}
isModuleInterface(node: $FlowFixMe): boolean {
return (
node.type === 'InterfaceDeclaration' &&
node.extends.length === 1 &&
node.extends[0].type === 'InterfaceExtends' &&
node.extends[0].id.name === 'TurboModule'
);
}
isGenericTypeAnnotation(type: $FlowFixMe): boolean {
return true;
}
extractAnnotatedElement(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
): $FlowFixMe {
return types[typeAnnotation.typeParameters.params[0].id.name];
}
getTypes(ast: $FlowFixMe): TypeDeclarationMap {
return {};
}
callExpressionTypeParameters(callExpression: $FlowFixMe): $FlowFixMe | null {
return callExpression.typeArguments || null;
}
computePartialProperties(
properties: Array<$FlowFixMe>,
hasteModuleName: string,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
): Array<$FlowFixMe> {
return [
{
name: 'a',
optional: true,
typeAnnotation: {type: 'StringTypeAnnotation'},
},
{
name: 'b',
optional: true,
typeAnnotation: {type: 'BooleanTypeAnnotation'},
},
];
}
functionTypeAnnotation(propertyValueType: string): boolean {
return propertyValueType === 'FunctionTypeAnnotation';
}
getTypeArgumentParamsFromDeclaration(declaration: $FlowFixMe): $FlowFixMe {
return declaration.typeArguments.params;
}
getNativeComponentType(
typeArgumentParams: $FlowFixMe,
funcArgumentParams: $FlowFixMe,
): {[string]: string} {
return {
propsTypeName: typeArgumentParams[0].id.name,
componentName: funcArgumentParams[0].value,
};
}
getAnnotatedElementProperties(annotatedElement: $FlowFixMe): $FlowFixMe {
return annotatedElement.right.properties;
}
bodyProperties(typeAlias: $FlowFixMe): $ReadOnlyArray<$FlowFixMe> {
return typeAlias.body.properties;
}
convertKeywordToTypeAnnotation(keyword: string): string {
return keyword;
}
argumentForProp(prop: PropAST): $FlowFixMe {
return prop.expression;
}
nameForArgument(prop: PropAST): $FlowFixMe {
return prop.expression.name;
}
isOptionalProperty(property: $FlowFixMe): boolean {
return property.optional || false;
}
getTypeAnnotationFromProperty(property: PropAST): $FlowFixMe {
return property.typeAnnotation.typeAnnotation;
}
getGetTypeAnnotationFN(): GetTypeAnnotationFN {
return () => {
return {};
};
}
getGetSchemaInfoFN(): GetSchemaInfoFN {
return () => {
return {
name: 'MockedSchema',
optional: false,
typeAnnotation: 'BooleanTypeAnnotation',
defaultValue: false,
withNullDefault: false,
};
};
}
getResolveTypeAnnotationFN(): ResolveTypeAnnotationFN {
return () => {
return {
nullable: false,
typeAnnotation: null,
typeResolutionStatus: {successful: false},
};
};
}
getResolvedTypeAnnotation(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
): {
nullable: boolean,
typeAnnotation: $FlowFixMe,
typeResolutionStatus: TypeResolutionStatus,
} {
invariant(
typeAnnotation != null,
'resolveTypeAnnotation(): typeAnnotation cannot be null',
);
let node = typeAnnotation;
let nullable = false;
let typeResolutionStatus: TypeResolutionStatus = {
successful: false,
};
for (;;) {
if (node.type === 'NullableTypeAnnotation') {
nullable = true;
node = node.typeAnnotation;
continue;
}
if (node.type !== 'GenericTypeAnnotation') {
break;
}
const resolvedTypeAnnotation = types[node.id.name];
if (resolvedTypeAnnotation == null) {
break;
}
switch (resolvedTypeAnnotation.type) {
case 'TypeAlias': {
typeResolutionStatus = {
successful: true,
type: 'alias',
name: node.id.name,
};
node = resolvedTypeAnnotation.right;
break;
}
case '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 ('TypeAlias') or enum ('EnumDeclaration'). Instead, got the unsupported ${resolvedTypeAnnotation.type}.`,
);
}
}
}
return {
nullable: nullable,
typeAnnotation: node,
typeResolutionStatus,
};
}
getExtendsProps(
typeDefinition: $ReadOnlyArray<PropAST>,
types: TypeDeclarationMap,
): $ReadOnlyArray<ExtendsPropsShape> {
return typeDefinition
.filter(prop => prop.type === 'ObjectTypeSpreadProperty')
.map(prop => this.extendsForProp(prop, types, this))
.filter(Boolean);
}
extendsForProp(
prop: PropAST,
types: TypeDeclarationMap,
parser: Parser,
): ExtendsForProp {
const argument = this.argumentForProp(prop);
if (!argument) {
console.log('null', prop);
}
const name = parser.nameForArgument(prop);
if (types[name] != null) {
// This type is locally defined in the file
return null;
}
switch (name) {
case 'ViewProps':
return {
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
};
default: {
throw new Error(`Unable to handle prop spread: ${name}`);
}
}
}
removeKnownExtends(
typeDefinition: $ReadOnlyArray<PropAST>,
types: TypeDeclarationMap,
): $ReadOnlyArray<PropAST> {
return typeDefinition.filter(
prop =>
prop.type !== 'ObjectTypeSpreadProperty' ||
this.extendsForProp(prop, types, this) === null,
);
}
getProps(
typeDefinition: $ReadOnlyArray<PropAST>,
types: TypeDeclarationMap,
): {
props: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
extendsProps: $ReadOnlyArray<ExtendsPropsShape>,
} {
const nonExtendsProps = this.removeKnownExtends(typeDefinition, types);
const props = flattenProperties(nonExtendsProps, types, this)
.map(property => buildPropSchema(property, types, this))
.filter(Boolean);
return {
props,
extendsProps: this.getExtendsProps(typeDefinition, types),
};
}
getProperties(typeName: string, types: TypeDeclarationMap): $FlowFixMe {
const typeAlias = types[typeName];
try {
return typeAlias.right.typeParameters.params[0].properties;
} catch (e) {
throw new Error(
`Failed to find type definition for "${typeName}", please check that you have a valid codegen flow file`,
);
}
}
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}.`;
}
extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string {
return typeAnnotation.type === 'GenericTypeAnnotation'
? typeAnnotation.id.name
: typeAnnotation.type;
}
getObjectProperties(typeAnnotation: $FlowFixMe): $FlowFixMe {
return typeAnnotation.properties;
}
getLiteralValue(option: $FlowFixMe): $FlowFixMe {
return option.value;
}
getPaperTopLevelNameDeprecated(typeAnnotation: $FlowFixMe): $FlowFixMe {
return typeAnnotation.typeParameters.params.length > 1
? typeAnnotation.typeParameters.params[1].value
: null;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,647 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {
throwIfArrayElementTypeAnnotationIsUnsupported,
throwIfPartialNotAnnotatingTypeParameter,
throwIfPartialWithMoreParameter,
} = require('./error-utils');
const {
ParserError,
UnsupportedTypeAnnotationParserError,
UnsupportedUnionTypeAnnotationParserError,
} = require('./errors');
const {
assertGenericTypeAnnotationHasExactlyOneTypeParameter,
translateFunctionTypeAnnotation,
unwrapNullable,
wrapNullable,
} = require('./parsers-commons');
const {nullGuard} = require('./parsers-utils');
const {isModuleRegistryCall} = require('./utils');
function emitBoolean(nullable) {
return wrapNullable(nullable, {
type: 'BooleanTypeAnnotation',
});
}
function emitInt32(nullable) {
return wrapNullable(nullable, {
type: 'Int32TypeAnnotation',
});
}
function emitInt32Prop(name, optional) {
return {
name,
optional,
typeAnnotation: {
type: 'Int32TypeAnnotation',
},
};
}
function emitNumber(nullable) {
return wrapNullable(nullable, {
type: 'NumberTypeAnnotation',
});
}
function emitRootTag(nullable) {
return wrapNullable(nullable, {
type: 'ReservedTypeAnnotation',
name: 'RootTag',
});
}
function emitDouble(nullable) {
return wrapNullable(nullable, {
type: 'DoubleTypeAnnotation',
});
}
function emitDoubleProp(name, optional) {
return {
name,
optional,
typeAnnotation: {
type: 'DoubleTypeAnnotation',
},
};
}
function emitVoid(nullable) {
return wrapNullable(nullable, {
type: 'VoidTypeAnnotation',
});
}
function emitStringish(nullable) {
return wrapNullable(nullable, {
type: 'StringTypeAnnotation',
});
}
function emitFunction(
nullable,
hasteModuleName,
typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
parser,
) {
const translateFunctionTypeAnnotationValue = translateFunctionTypeAnnotation(
hasteModuleName,
typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
parser,
);
return wrapNullable(nullable, translateFunctionTypeAnnotationValue);
}
function emitMixed(nullable) {
return wrapNullable(nullable, {
type: 'MixedTypeAnnotation',
});
}
function emitNumberLiteral(nullable, value) {
return wrapNullable(nullable, {
type: 'NumberLiteralTypeAnnotation',
value,
});
}
function emitString(nullable) {
return wrapNullable(nullable, {
type: 'StringTypeAnnotation',
});
}
function emitStringLiteral(nullable, value) {
return wrapNullable(nullable, {
type: 'StringLiteralTypeAnnotation',
value,
});
}
function emitStringProp(name, optional) {
return {
name,
optional,
typeAnnotation: {
type: 'StringTypeAnnotation',
},
};
}
function typeAliasResolution(
typeResolution,
objectTypeAnnotation,
aliasMap,
nullable,
) {
if (!typeResolution.successful) {
return wrapNullable(nullable, objectTypeAnnotation);
}
/**
* All aliases RHS are required.
*/
aliasMap[typeResolution.name] = objectTypeAnnotation;
/**
* Nullability of type aliases is transitive.
*
* Consider this case:
*
* type Animal = ?{
* name: string,
* };
*
* type B = Animal
*
* export interface Spec extends TurboModule {
* +greet: (animal: B) => void;
* }
*
* In this case, we follow B to Animal, and then Animal to ?{name: string}.
*
* We:
* 1. Replace `+greet: (animal: B) => void;` with `+greet: (animal: ?Animal) => void;`,
* 2. Pretend that Animal = {name: string}.
*
* Why do we do this?
* 1. In ObjC, we need to generate a struct called Animal, not B.
* 2. This design is simpler than managing nullability within both the type alias usage, and the type alias RHS.
* 3. What does it mean for a C++ struct, which is what this type alias RHS will generate, to be nullable? ¯\_(ツ)_/¯
* Nullability is a concept that only makes sense when talking about instances (i.e: usages) of the C++ structs.
* Hence, it's better to manage nullability within the actual TypeAliasTypeAnnotation nodes, and not the
* associated ObjectTypeAnnotations.
*/
return wrapNullable(nullable, {
type: 'TypeAliasTypeAnnotation',
name: typeResolution.name,
});
}
function typeEnumResolution(
typeAnnotation,
typeResolution,
nullable,
hasteModuleName,
enumMap,
parser,
) {
if (!typeResolution.successful || typeResolution.type !== 'enum') {
throw new UnsupportedTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
const enumName = typeResolution.name;
const enumMemberType = parser.parseEnumMembersType(typeAnnotation);
try {
parser.validateEnumMembersSupported(typeAnnotation, enumMemberType);
} catch (e) {
if (e instanceof Error) {
throw new ParserError(
hasteModuleName,
typeAnnotation,
`Failed parsing the enum ${enumName} in ${hasteModuleName} with the error: ${e.message}`,
);
} else {
throw e;
}
}
const enumMembers = parser.parseEnumMembers(typeAnnotation);
enumMap[enumName] = {
name: enumName,
type: 'EnumDeclarationWithMembers',
memberType: enumMemberType,
members: enumMembers,
};
return wrapNullable(nullable, {
name: enumName,
type: 'EnumDeclaration',
memberType: enumMemberType,
});
}
function emitPromise(
hasteModuleName,
typeAnnotation,
parser,
nullable,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
) {
assertGenericTypeAnnotationHasExactlyOneTypeParameter(
hasteModuleName,
typeAnnotation,
parser,
);
const elementType = typeAnnotation.typeParameters.params[0];
if (
elementType.type === 'ExistsTypeAnnotation' ||
elementType.type === 'EmptyTypeAnnotation'
) {
return wrapNullable(nullable, {
type: 'PromiseTypeAnnotation',
elementType: {
type: 'VoidTypeAnnotation',
},
});
} else {
try {
return wrapNullable(nullable, {
type: 'PromiseTypeAnnotation',
elementType: translateTypeAnnotation(
hasteModuleName,
typeAnnotation.typeParameters.params[0],
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
),
});
} catch {
return wrapNullable(nullable, {
type: 'PromiseTypeAnnotation',
elementType: {
type: 'VoidTypeAnnotation',
},
});
}
}
}
function emitGenericObject(nullable) {
return wrapNullable(nullable, {
type: 'GenericObjectTypeAnnotation',
});
}
function emitDictionary(nullable, valueType) {
return wrapNullable(nullable, {
type: 'GenericObjectTypeAnnotation',
dictionaryValueType: valueType,
});
}
function emitObject(nullable, properties) {
return wrapNullable(nullable, {
type: 'ObjectTypeAnnotation',
properties,
});
}
function emitFloat(nullable) {
return wrapNullable(nullable, {
type: 'FloatTypeAnnotation',
});
}
function emitFloatProp(name, optional) {
return {
name,
optional,
typeAnnotation: {
type: 'FloatTypeAnnotation',
},
};
}
function emitUnion(nullable, hasteModuleName, typeAnnotation, parser) {
// Get all the literals by type
// Verify they are all the same
// If string, persist as StringLiteralUnionType
// If number, persist as NumberTypeAnnotation (TODO: Number literal)
const unionTypes = parser.remapUnionTypeAnnotationMemberNames(
typeAnnotation.types,
);
// Only support unionTypes of the same kind
if (unionTypes.length > 1) {
throw new UnsupportedUnionTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
unionTypes,
);
}
if (unionTypes[0] === 'StringTypeAnnotation') {
// Reprocess as a string literal union
return emitStringLiteralUnion(
nullable,
hasteModuleName,
typeAnnotation,
parser,
);
}
return wrapNullable(nullable, {
type: 'UnionTypeAnnotation',
memberType: unionTypes[0],
});
}
function emitStringLiteralUnion(
nullable,
hasteModuleName,
typeAnnotation,
parser,
) {
const stringLiterals =
parser.getStringLiteralUnionTypeAnnotationStringLiterals(
typeAnnotation.types,
);
return wrapNullable(nullable, {
type: 'StringLiteralUnionTypeAnnotation',
types: stringLiterals.map(stringLiteral => ({
type: 'StringLiteralTypeAnnotation',
value: stringLiteral,
})),
});
}
function translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
enumMap,
cxxOnly,
arrayType,
elementType,
nullable,
translateTypeAnnotation,
parser,
) {
try {
/**
* TODO(T72031674): Migrate all our NativeModule specs to not use
* invalid Array ElementTypes. Then, make the elementType a required
* parameter.
*/
const [_elementType, isElementTypeNullable] = unwrapNullable(
translateTypeAnnotation(
hasteModuleName,
elementType,
types,
aliasMap,
enumMap,
/**
* TODO(T72031674): Ensure that all ParsingErrors that are thrown
* while parsing the array element don't get captured and collected.
* Why? If we detect any parsing error while parsing the element,
* we should default it to null down the line, here. This is
* the correct behaviour until we migrate all our NativeModule specs
* to be parseable.
*/
nullGuard,
cxxOnly,
parser,
),
);
throwIfArrayElementTypeAnnotationIsUnsupported(
hasteModuleName,
elementType,
arrayType,
_elementType.type,
);
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
// $FlowFixMe[incompatible-type]
elementType: wrapNullable(isElementTypeNullable, _elementType),
});
} catch (ex) {
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'AnyTypeAnnotation',
},
});
}
}
function emitArrayType(
hasteModuleName,
typeAnnotation,
parser,
types,
aliasMap,
enumMap,
cxxOnly,
nullable,
translateTypeAnnotation,
) {
assertGenericTypeAnnotationHasExactlyOneTypeParameter(
hasteModuleName,
typeAnnotation,
parser,
);
return translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
enumMap,
cxxOnly,
typeAnnotation.type,
typeAnnotation.typeParameters.params[0],
nullable,
translateTypeAnnotation,
parser,
);
}
function Visitor(infoMap) {
return {
CallExpression(node) {
if (
node.callee.type === 'Identifier' &&
node.callee.name === 'codegenNativeComponent'
) {
infoMap.isComponent = true;
}
if (isModuleRegistryCall(node)) {
infoMap.isModule = true;
}
},
InterfaceExtends(node) {
if (node.id.name === 'TurboModule') {
infoMap.isModule = true;
}
},
TSInterfaceDeclaration(node) {
if (
Array.isArray(node.extends) &&
node.extends.some(
extension => extension.expression.name === 'TurboModule',
)
) {
infoMap.isModule = true;
}
},
};
}
function emitPartial(
nullable,
hasteModuleName,
typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
) {
throwIfPartialWithMoreParameter(typeAnnotation);
throwIfPartialNotAnnotatingTypeParameter(typeAnnotation, types, parser);
const annotatedElement = parser.extractAnnotatedElement(
typeAnnotation,
types,
);
const annotatedElementProperties =
parser.getAnnotatedElementProperties(annotatedElement);
const partialProperties = parser.computePartialProperties(
annotatedElementProperties,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
);
return emitObject(nullable, partialProperties);
}
function emitCommonTypes(
hasteModuleName,
types,
typeAnnotation,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
parser,
) {
const typeMap = {
Stringish: emitStringish,
Int32: emitInt32,
Double: emitDouble,
Float: emitFloat,
UnsafeObject: emitGenericObject,
Object: emitGenericObject,
$Partial: emitPartial,
Partial: emitPartial,
BooleanTypeAnnotation: emitBoolean,
NumberTypeAnnotation: emitNumber,
VoidTypeAnnotation: emitVoid,
StringTypeAnnotation: emitString,
MixedTypeAnnotation: cxxOnly ? emitMixed : emitGenericObject,
};
const typeAnnotationName = parser.convertKeywordToTypeAnnotation(
typeAnnotation.type,
);
// $FlowFixMe[invalid-computed-prop]
const simpleEmitter = typeMap[typeAnnotationName];
if (simpleEmitter) {
return simpleEmitter(nullable);
}
const genericTypeAnnotationName =
parser.getTypeAnnotationName(typeAnnotation);
// $FlowFixMe[invalid-computed-prop]
const emitter = typeMap[genericTypeAnnotationName];
if (!emitter) {
return null;
}
return emitter(
nullable,
hasteModuleName,
typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
function emitBoolProp(name, optional) {
return {
name,
optional,
typeAnnotation: {
type: 'BooleanTypeAnnotation',
},
};
}
function emitMixedProp(name, optional) {
return {
name,
optional,
typeAnnotation: {
type: 'MixedTypeAnnotation',
},
};
}
function emitObjectProp(
name,
optional,
parser,
typeAnnotation,
extractArrayElementType,
) {
return {
name,
optional,
typeAnnotation: extractArrayElementType(typeAnnotation, name, parser),
};
}
function emitUnionProp(name, optional, parser, typeAnnotation) {
return {
name,
optional,
typeAnnotation: {
type: 'StringLiteralUnionTypeAnnotation',
types: typeAnnotation.types.map(option => ({
type: 'StringLiteralTypeAnnotation',
value: parser.getLiteralValue(option),
})),
},
};
}
module.exports = {
emitArrayType,
emitBoolean,
emitBoolProp,
emitDouble,
emitDoubleProp,
emitFloat,
emitFloatProp,
emitFunction,
emitInt32,
emitInt32Prop,
emitMixedProp,
emitNumber,
emitNumberLiteral,
emitGenericObject,
emitDictionary,
emitObject,
emitPromise,
emitRootTag,
emitVoid,
emitString,
emitStringish,
emitStringProp,
emitStringLiteral,
emitMixed,
emitUnion,
emitPartial,
emitCommonTypes,
typeAliasResolution,
typeEnumResolution,
translateArrayTypeAnnotation,
Visitor,
emitObjectProp,
emitUnionProp,
};

View File

@@ -0,0 +1,798 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
BooleanTypeAnnotation,
DoubleTypeAnnotation,
EventTypeAnnotation,
FloatTypeAnnotation,
Int32TypeAnnotation,
NamedShape,
NativeModuleAliasMap,
NativeModuleBaseTypeAnnotation,
NativeModuleEnumDeclaration,
NativeModuleEnumMap,
NativeModuleFunctionTypeAnnotation,
NativeModuleGenericObjectTypeAnnotation,
NativeModuleMixedTypeAnnotation,
NativeModuleNumberTypeAnnotation,
NativeModuleObjectTypeAnnotation,
NativeModulePromiseTypeAnnotation,
NativeModuleTypeAliasTypeAnnotation,
NativeModuleTypeAnnotation,
NativeModuleUnionTypeAnnotation,
Nullable,
NumberLiteralTypeAnnotation,
ObjectTypeAnnotation,
ReservedTypeAnnotation,
StringLiteralTypeAnnotation,
StringLiteralUnionTypeAnnotation,
StringTypeAnnotation,
VoidTypeAnnotation,
} from '../CodegenSchema';
import type {Parser} from './parser';
import type {
ParserErrorCapturer,
TypeDeclarationMap,
TypeResolutionStatus,
} from './utils';
const {
throwIfArrayElementTypeAnnotationIsUnsupported,
throwIfPartialNotAnnotatingTypeParameter,
throwIfPartialWithMoreParameter,
} = require('./error-utils');
const {
ParserError,
UnsupportedTypeAnnotationParserError,
UnsupportedUnionTypeAnnotationParserError,
} = require('./errors');
const {
assertGenericTypeAnnotationHasExactlyOneTypeParameter,
translateFunctionTypeAnnotation,
unwrapNullable,
wrapNullable,
} = require('./parsers-commons');
const {nullGuard} = require('./parsers-utils');
const {isModuleRegistryCall} = require('./utils');
function emitBoolean(nullable: boolean): Nullable<BooleanTypeAnnotation> {
return wrapNullable(nullable, {
type: 'BooleanTypeAnnotation',
});
}
function emitInt32(nullable: boolean): Nullable<Int32TypeAnnotation> {
return wrapNullable(nullable, {
type: 'Int32TypeAnnotation',
});
}
function emitInt32Prop(
name: string,
optional: boolean,
): NamedShape<Int32TypeAnnotation> {
return {
name,
optional,
typeAnnotation: {
type: 'Int32TypeAnnotation',
},
};
}
function emitNumber(
nullable: boolean,
): Nullable<NativeModuleNumberTypeAnnotation> {
return wrapNullable(nullable, {
type: 'NumberTypeAnnotation',
});
}
function emitRootTag(nullable: boolean): Nullable<ReservedTypeAnnotation> {
return wrapNullable(nullable, {
type: 'ReservedTypeAnnotation',
name: 'RootTag',
});
}
function emitDouble(nullable: boolean): Nullable<DoubleTypeAnnotation> {
return wrapNullable(nullable, {
type: 'DoubleTypeAnnotation',
});
}
function emitDoubleProp(
name: string,
optional: boolean,
): NamedShape<DoubleTypeAnnotation> {
return {
name,
optional,
typeAnnotation: {
type: 'DoubleTypeAnnotation',
},
};
}
function emitVoid(nullable: boolean): Nullable<VoidTypeAnnotation> {
return wrapNullable(nullable, {
type: 'VoidTypeAnnotation',
});
}
function emitStringish(nullable: boolean): Nullable<StringTypeAnnotation> {
return wrapNullable(nullable, {
type: 'StringTypeAnnotation',
});
}
function emitFunction(
nullable: boolean,
hasteModuleName: string,
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
translateTypeAnnotation: $FlowFixMe,
parser: Parser,
): Nullable<NativeModuleFunctionTypeAnnotation> {
const translateFunctionTypeAnnotationValue: NativeModuleFunctionTypeAnnotation =
translateFunctionTypeAnnotation(
hasteModuleName,
typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
parser,
);
return wrapNullable(nullable, translateFunctionTypeAnnotationValue);
}
function emitMixed(
nullable: boolean,
): Nullable<NativeModuleMixedTypeAnnotation> {
return wrapNullable(nullable, {
type: 'MixedTypeAnnotation',
});
}
function emitNumberLiteral(
nullable: boolean,
value: number,
): Nullable<NumberLiteralTypeAnnotation> {
return wrapNullable(nullable, {
type: 'NumberLiteralTypeAnnotation',
value,
});
}
function emitString(nullable: boolean): Nullable<StringTypeAnnotation> {
return wrapNullable(nullable, {
type: 'StringTypeAnnotation',
});
}
function emitStringLiteral(
nullable: boolean,
value: string,
): Nullable<StringLiteralTypeAnnotation> {
return wrapNullable(nullable, {
type: 'StringLiteralTypeAnnotation',
value,
});
}
function emitStringProp(
name: string,
optional: boolean,
): NamedShape<StringTypeAnnotation> {
return {
name,
optional,
typeAnnotation: {
type: 'StringTypeAnnotation',
},
};
}
function typeAliasResolution(
typeResolution: TypeResolutionStatus,
objectTypeAnnotation: ObjectTypeAnnotation<
Nullable<NativeModuleBaseTypeAnnotation>,
>,
aliasMap: {...NativeModuleAliasMap},
nullable: boolean,
):
| Nullable<NativeModuleTypeAliasTypeAnnotation>
| Nullable<ObjectTypeAnnotation<Nullable<NativeModuleBaseTypeAnnotation>>> {
if (!typeResolution.successful) {
return wrapNullable(nullable, objectTypeAnnotation);
}
/**
* All aliases RHS are required.
*/
aliasMap[typeResolution.name] = objectTypeAnnotation;
/**
* Nullability of type aliases is transitive.
*
* Consider this case:
*
* type Animal = ?{
* name: string,
* };
*
* type B = Animal
*
* export interface Spec extends TurboModule {
* +greet: (animal: B) => void;
* }
*
* In this case, we follow B to Animal, and then Animal to ?{name: string}.
*
* We:
* 1. Replace `+greet: (animal: B) => void;` with `+greet: (animal: ?Animal) => void;`,
* 2. Pretend that Animal = {name: string}.
*
* Why do we do this?
* 1. In ObjC, we need to generate a struct called Animal, not B.
* 2. This design is simpler than managing nullability within both the type alias usage, and the type alias RHS.
* 3. What does it mean for a C++ struct, which is what this type alias RHS will generate, to be nullable? ¯\_(ツ)_/¯
* Nullability is a concept that only makes sense when talking about instances (i.e: usages) of the C++ structs.
* Hence, it's better to manage nullability within the actual TypeAliasTypeAnnotation nodes, and not the
* associated ObjectTypeAnnotations.
*/
return wrapNullable(nullable, {
type: 'TypeAliasTypeAnnotation',
name: typeResolution.name,
});
}
function typeEnumResolution(
typeAnnotation: $FlowFixMe,
typeResolution: TypeResolutionStatus,
nullable: boolean,
hasteModuleName: string,
enumMap: {...NativeModuleEnumMap},
parser: Parser,
): Nullable<NativeModuleEnumDeclaration> {
if (!typeResolution.successful || typeResolution.type !== 'enum') {
throw new UnsupportedTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
const enumName = typeResolution.name;
const enumMemberType = parser.parseEnumMembersType(typeAnnotation);
try {
parser.validateEnumMembersSupported(typeAnnotation, enumMemberType);
} catch (e) {
if (e instanceof Error) {
throw new ParserError(
hasteModuleName,
typeAnnotation,
`Failed parsing the enum ${enumName} in ${hasteModuleName} with the error: ${e.message}`,
);
} else {
throw e;
}
}
const enumMembers = parser.parseEnumMembers(typeAnnotation);
enumMap[enumName] = {
name: enumName,
type: 'EnumDeclarationWithMembers',
memberType: enumMemberType,
members: enumMembers,
};
return wrapNullable(nullable, {
name: enumName,
type: 'EnumDeclaration',
memberType: enumMemberType,
});
}
function emitPromise(
hasteModuleName: string,
typeAnnotation: $FlowFixMe,
parser: Parser,
nullable: boolean,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
translateTypeAnnotation: $FlowFixMe,
): Nullable<NativeModulePromiseTypeAnnotation> {
assertGenericTypeAnnotationHasExactlyOneTypeParameter(
hasteModuleName,
typeAnnotation,
parser,
);
const elementType = typeAnnotation.typeParameters.params[0];
if (
elementType.type === 'ExistsTypeAnnotation' ||
elementType.type === 'EmptyTypeAnnotation'
) {
return wrapNullable(nullable, {
type: 'PromiseTypeAnnotation',
elementType: {
type: 'VoidTypeAnnotation',
},
});
} else {
try {
return wrapNullable(nullable, {
type: 'PromiseTypeAnnotation',
elementType: translateTypeAnnotation(
hasteModuleName,
typeAnnotation.typeParameters.params[0],
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
),
});
} catch {
return wrapNullable(nullable, {
type: 'PromiseTypeAnnotation',
elementType: {
type: 'VoidTypeAnnotation',
},
});
}
}
}
function emitGenericObject(
nullable: boolean,
): Nullable<NativeModuleGenericObjectTypeAnnotation> {
return wrapNullable(nullable, {
type: 'GenericObjectTypeAnnotation',
});
}
function emitDictionary(
nullable: boolean,
valueType: Nullable<NativeModuleTypeAnnotation>,
): Nullable<NativeModuleGenericObjectTypeAnnotation> {
return wrapNullable(nullable, {
type: 'GenericObjectTypeAnnotation',
dictionaryValueType: valueType,
});
}
function emitObject(
nullable: boolean,
properties: Array<$FlowFixMe>,
): Nullable<NativeModuleObjectTypeAnnotation> {
return wrapNullable(nullable, {
type: 'ObjectTypeAnnotation',
properties,
});
}
function emitFloat(nullable: boolean): Nullable<FloatTypeAnnotation> {
return wrapNullable(nullable, {
type: 'FloatTypeAnnotation',
});
}
function emitFloatProp(
name: string,
optional: boolean,
): NamedShape<EventTypeAnnotation> {
return {
name,
optional,
typeAnnotation: {
type: 'FloatTypeAnnotation',
},
};
}
function emitUnion(
nullable: boolean,
hasteModuleName: string,
typeAnnotation: $FlowFixMe,
parser: Parser,
): Nullable<
NativeModuleUnionTypeAnnotation | StringLiteralUnionTypeAnnotation,
> {
// Get all the literals by type
// Verify they are all the same
// If string, persist as StringLiteralUnionType
// If number, persist as NumberTypeAnnotation (TODO: Number literal)
const unionTypes = parser.remapUnionTypeAnnotationMemberNames(
typeAnnotation.types,
);
// Only support unionTypes of the same kind
if (unionTypes.length > 1) {
throw new UnsupportedUnionTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
unionTypes,
);
}
if (unionTypes[0] === 'StringTypeAnnotation') {
// Reprocess as a string literal union
return emitStringLiteralUnion(
nullable,
hasteModuleName,
typeAnnotation,
parser,
);
}
return wrapNullable(nullable, {
type: 'UnionTypeAnnotation',
memberType: unionTypes[0],
});
}
function emitStringLiteralUnion(
nullable: boolean,
hasteModuleName: string,
typeAnnotation: $FlowFixMe,
parser: Parser,
): Nullable<StringLiteralUnionTypeAnnotation> {
const stringLiterals =
parser.getStringLiteralUnionTypeAnnotationStringLiterals(
typeAnnotation.types,
);
return wrapNullable(nullable, {
type: 'StringLiteralUnionTypeAnnotation',
types: stringLiterals.map(stringLiteral => ({
type: 'StringLiteralTypeAnnotation',
value: stringLiteral,
})),
});
}
function translateArrayTypeAnnotation(
hasteModuleName: string,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
cxxOnly: boolean,
arrayType: 'Array' | 'ReadonlyArray',
elementType: $FlowFixMe,
nullable: boolean,
translateTypeAnnotation: $FlowFixMe,
parser: Parser,
): Nullable<NativeModuleTypeAnnotation> {
try {
/**
* TODO(T72031674): Migrate all our NativeModule specs to not use
* invalid Array ElementTypes. Then, make the elementType a required
* parameter.
*/
const [_elementType, isElementTypeNullable] = unwrapNullable<$FlowFixMe>(
translateTypeAnnotation(
hasteModuleName,
elementType,
types,
aliasMap,
enumMap,
/**
* TODO(T72031674): Ensure that all ParsingErrors that are thrown
* while parsing the array element don't get captured and collected.
* Why? If we detect any parsing error while parsing the element,
* we should default it to null down the line, here. This is
* the correct behaviour until we migrate all our NativeModule specs
* to be parseable.
*/
nullGuard,
cxxOnly,
parser,
),
);
throwIfArrayElementTypeAnnotationIsUnsupported(
hasteModuleName,
elementType,
arrayType,
_elementType.type,
);
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
// $FlowFixMe[incompatible-type]
elementType: wrapNullable(isElementTypeNullable, _elementType),
});
} catch (ex) {
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'AnyTypeAnnotation',
},
});
}
}
function emitArrayType(
hasteModuleName: string,
typeAnnotation: $FlowFixMe,
parser: Parser,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
cxxOnly: boolean,
nullable: boolean,
translateTypeAnnotation: $FlowFixMe,
): Nullable<NativeModuleTypeAnnotation> {
assertGenericTypeAnnotationHasExactlyOneTypeParameter(
hasteModuleName,
typeAnnotation,
parser,
);
return translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
enumMap,
cxxOnly,
typeAnnotation.type,
typeAnnotation.typeParameters.params[0],
nullable,
translateTypeAnnotation,
parser,
);
}
function Visitor(infoMap: {isComponent: boolean, isModule: boolean}): {
[type: string]: (node: $FlowFixMe) => void,
} {
return {
CallExpression(node: $FlowFixMe) {
if (
node.callee.type === 'Identifier' &&
node.callee.name === 'codegenNativeComponent'
) {
infoMap.isComponent = true;
}
if (isModuleRegistryCall(node)) {
infoMap.isModule = true;
}
},
InterfaceExtends(node: $FlowFixMe) {
if (node.id.name === 'TurboModule') {
infoMap.isModule = true;
}
},
TSInterfaceDeclaration(node: $FlowFixMe) {
if (
Array.isArray(node.extends) &&
node.extends.some(
extension => extension.expression.name === 'TurboModule',
)
) {
infoMap.isModule = true;
}
},
};
}
function emitPartial(
nullable: boolean,
hasteModuleName: string,
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
parser: Parser,
): Nullable<NativeModuleTypeAnnotation> {
throwIfPartialWithMoreParameter(typeAnnotation);
throwIfPartialNotAnnotatingTypeParameter(typeAnnotation, types, parser);
const annotatedElement = parser.extractAnnotatedElement(
typeAnnotation,
types,
);
const annotatedElementProperties =
parser.getAnnotatedElementProperties(annotatedElement);
const partialProperties = parser.computePartialProperties(
annotatedElementProperties,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
);
return emitObject(nullable, partialProperties);
}
function emitCommonTypes(
hasteModuleName: string,
types: TypeDeclarationMap,
typeAnnotation: $FlowFixMe,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
nullable: boolean,
parser: Parser,
): $FlowFixMe {
const typeMap = {
Stringish: emitStringish,
Int32: emitInt32,
Double: emitDouble,
Float: emitFloat,
UnsafeObject: emitGenericObject,
Object: emitGenericObject,
$Partial: emitPartial,
Partial: emitPartial,
BooleanTypeAnnotation: emitBoolean,
NumberTypeAnnotation: emitNumber,
VoidTypeAnnotation: emitVoid,
StringTypeAnnotation: emitString,
MixedTypeAnnotation: cxxOnly ? emitMixed : emitGenericObject,
};
const typeAnnotationName = parser.convertKeywordToTypeAnnotation(
typeAnnotation.type,
);
// $FlowFixMe[invalid-computed-prop]
const simpleEmitter = typeMap[typeAnnotationName];
if (simpleEmitter) {
return simpleEmitter(nullable);
}
const genericTypeAnnotationName =
parser.getTypeAnnotationName(typeAnnotation);
// $FlowFixMe[invalid-computed-prop]
const emitter = typeMap[genericTypeAnnotationName];
if (!emitter) {
return null;
}
return emitter(
nullable,
hasteModuleName,
typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
function emitBoolProp(
name: string,
optional: boolean,
): NamedShape<EventTypeAnnotation> {
return {
name,
optional,
typeAnnotation: {
type: 'BooleanTypeAnnotation',
},
};
}
function emitMixedProp(
name: string,
optional: boolean,
): NamedShape<EventTypeAnnotation> {
return {
name,
optional,
typeAnnotation: {
type: 'MixedTypeAnnotation',
},
};
}
function emitObjectProp(
name: string,
optional: boolean,
parser: Parser,
typeAnnotation: $FlowFixMe,
extractArrayElementType: (
typeAnnotation: $FlowFixMe,
name: string,
parser: Parser,
) => EventTypeAnnotation,
): NamedShape<EventTypeAnnotation> {
return {
name,
optional,
typeAnnotation: extractArrayElementType(typeAnnotation, name, parser),
};
}
function emitUnionProp(
name: string,
optional: boolean,
parser: Parser,
typeAnnotation: $FlowFixMe,
): NamedShape<EventTypeAnnotation> {
return {
name,
optional,
typeAnnotation: {
type: 'StringLiteralUnionTypeAnnotation',
types: typeAnnotation.types.map(option => ({
type: 'StringLiteralTypeAnnotation',
value: parser.getLiteralValue(option),
})),
},
};
}
module.exports = {
emitArrayType,
emitBoolean,
emitBoolProp,
emitDouble,
emitDoubleProp,
emitFloat,
emitFloatProp,
emitFunction,
emitInt32,
emitInt32Prop,
emitMixedProp,
emitNumber,
emitNumberLiteral,
emitGenericObject,
emitDictionary,
emitObject,
emitPromise,
emitRootTag,
emitVoid,
emitString,
emitStringish,
emitStringProp,
emitStringLiteral,
emitMixed,
emitUnion,
emitPartial,
emitCommonTypes,
typeAliasResolution,
typeEnumResolution,
translateArrayTypeAnnotation,
Visitor,
emitObjectProp,
emitUnionProp,
};

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function nullGuard(fn) {
return fn();
}
module.exports = {
nullGuard,
};

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
function nullGuard<T>(fn: () => T): ?T {
return fn();
}
module.exports = {
nullGuard,
};

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function wrapComponentSchema({
filename,
componentName,
extendsProps,
events,
props,
options,
commands,
}) {
return {
modules: {
[filename]: {
type: 'Component',
components: {
[componentName]: {
...(options || {}),
extendsProps,
events,
props,
commands,
},
},
},
},
};
}
module.exports = {
wrapComponentSchema,
};

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
CommandTypeAnnotation,
EventTypeShape,
ExtendsPropsShape,
NamedShape,
OptionsShape,
PropTypeAnnotation,
SchemaType,
} from '../CodegenSchema.js';
export type ComponentSchemaBuilderConfig = $ReadOnly<{
filename: string,
componentName: string,
extendsProps: $ReadOnlyArray<ExtendsPropsShape>,
events: $ReadOnlyArray<EventTypeShape>,
props: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
commands: $ReadOnlyArray<NamedShape<CommandTypeAnnotation>>,
options?: ?OptionsShape,
}>;
function wrapComponentSchema({
filename,
componentName,
extendsProps,
events,
props,
options,
commands,
}: ComponentSchemaBuilderConfig): SchemaType {
return {
modules: {
[filename]: {
type: 'Component',
components: {
[componentName]: {
...(options || {}),
extendsProps,
events,
props,
commands,
},
},
},
},
};
}
module.exports = {
wrapComponentSchema,
};

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { SchemaType } from '../../CodegenSchema';
export declare function parse(filename: string): SchemaType | undefined;

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function parse(filename) {
try {
// $FlowFixMe[unsupported-syntax] Can't require dynamic variables
return require(filename);
} catch (err) {
// Ignore
}
}
module.exports = {
parse,
};

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema.js';
function parse(filename: string): ?SchemaType {
try {
// $FlowFixMe[unsupported-syntax] Can't require dynamic variables
return require(filename);
} catch (err) {
// Ignore
}
}
module.exports = {
parse,
};

View File

@@ -0,0 +1,190 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {parseTopLevelType} = require('../parseTopLevelType');
const {getPrimitiveTypeAnnotation} = require('./componentsUtils');
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
function buildCommandSchemaInternal(name, optional, parameters, types, parser) {
var _firstParam$typeAnnot, _firstParam$typeAnnot2;
const firstParam = parameters[0].typeAnnotation;
if (
!(
firstParam.typeAnnotation != null &&
firstParam.typeAnnotation.type === 'TSTypeReference' &&
((_firstParam$typeAnnot = firstParam.typeAnnotation.typeName.left) ===
null || _firstParam$typeAnnot === void 0
? void 0
: _firstParam$typeAnnot.name) === 'React' &&
((_firstParam$typeAnnot2 = firstParam.typeAnnotation.typeName.right) ===
null || _firstParam$typeAnnot2 === void 0
? void 0
: _firstParam$typeAnnot2.name) === 'ElementRef'
)
) {
throw new Error(
`The first argument of method ${name} must be of type React.ElementRef<>`,
);
}
const params = parameters.slice(1).map(param => {
const paramName = param.name;
const paramValue = parseTopLevelType(
param.typeAnnotation.typeAnnotation,
parser,
types,
).type;
const type =
paramValue.type === 'TSTypeReference'
? parser.getTypeAnnotationName(paramValue)
: paramValue.type;
let returnType;
switch (type) {
case 'RootTag':
returnType = {
type: 'ReservedTypeAnnotation',
name: 'RootTag',
};
break;
case 'TSBooleanKeyword':
case 'Int32':
case 'Double':
case 'Float':
case 'TSStringKeyword':
returnType = getPrimitiveTypeAnnotation(type);
break;
case 'Array':
case 'ReadOnlyArray':
/* $FlowFixMe[invalid-compare] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/4oq3zi07. */
if (!paramValue.type === 'TSTypeReference') {
throw new Error(
'Array and ReadOnlyArray are TSTypeReference for array',
);
}
returnType = {
type: 'ArrayTypeAnnotation',
elementType: getCommandArrayElementTypeType(
paramValue.typeParameters.params[0],
parser,
),
};
break;
case 'TSArrayType':
returnType = {
type: 'ArrayTypeAnnotation',
elementType: getCommandArrayElementTypeType(
paramValue.elementType,
parser,
),
};
break;
default:
type;
throw new Error(
`Unsupported param type for method "${name}", param "${paramName}". Found ${type}`,
);
}
return {
name: paramName,
optional: false,
typeAnnotation: returnType,
};
});
return {
name,
optional,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
params,
returnTypeAnnotation: {
type: 'VoidTypeAnnotation',
},
},
};
}
function getCommandArrayElementTypeType(inputType, parser) {
// TODO: T172453752 support more complex type annotation for array element
if (inputType == null || typeof inputType !== 'object') {
throw new Error(`Expected an object, received ${typeof inputType}`);
}
const type = inputType.type;
if (typeof type !== 'string') {
throw new Error('Command array element type must be a string');
}
// This is not a great solution. This generally means its a type alias to another type
// like an object or union. Ideally we'd encode that in the schema so the compat-check can
// validate those deeper objects for breaking changes and the generators can do something smarter.
// As of now, the generators just create ReadableMap or (const NSArray *) which are untyped
if (type === 'TSTypeReference') {
const name =
typeof inputType.typeName === 'object'
? parser.getTypeAnnotationName(inputType)
: null;
if (typeof name !== 'string') {
throw new Error('Expected TSTypeReference AST name to be a string');
}
try {
return getPrimitiveTypeAnnotation(name);
} catch (e) {
return {
type: 'MixedTypeAnnotation',
};
}
}
return getPrimitiveTypeAnnotation(type);
}
function buildCommandSchema(property, types, parser) {
if (property.type === 'TSPropertySignature') {
const topLevelType = parseTopLevelType(
property.typeAnnotation.typeAnnotation,
parser,
types,
);
const name = property.key.name;
const optional = property.optional || topLevelType.optional;
const parameters = topLevelType.type.parameters || topLevelType.type.params;
return buildCommandSchemaInternal(
name,
optional,
parameters,
types,
parser,
);
} else {
const name = property.key.name;
const optional = property.optional || false;
const parameters = property.parameters || property.params;
return buildCommandSchemaInternal(
name,
optional,
parameters,
types,
parser,
);
}
}
function getCommands(commandTypeAST, types, parser) {
return commandTypeAST
.filter(
property =>
property.type === 'TSPropertySignature' ||
property.type === 'TSMethodSignature',
)
.map(property => buildCommandSchema(property, types, parser))
.filter(Boolean);
}
module.exports = {
getCommands,
};

View File

@@ -0,0 +1,223 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
CommandParamTypeAnnotation,
CommandTypeAnnotation,
ComponentCommandArrayTypeAnnotation,
NamedShape,
} from '../../../CodegenSchema.js';
import type {Parser} from '../../parser';
import type {TypeDeclarationMap} from '../../utils';
const {parseTopLevelType} = require('../parseTopLevelType');
const {getPrimitiveTypeAnnotation} = require('./componentsUtils');
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
type EventTypeAST = Object;
function buildCommandSchemaInternal(
name: string,
optional: boolean,
parameters: Array<$FlowFixMe>,
types: TypeDeclarationMap,
parser: Parser,
): NamedShape<CommandTypeAnnotation> {
const firstParam = parameters[0].typeAnnotation;
if (
!(
firstParam.typeAnnotation != null &&
firstParam.typeAnnotation.type === 'TSTypeReference' &&
firstParam.typeAnnotation.typeName.left?.name === 'React' &&
firstParam.typeAnnotation.typeName.right?.name === 'ElementRef'
)
) {
throw new Error(
`The first argument of method ${name} must be of type React.ElementRef<>`,
);
}
const params = parameters.slice(1).map(param => {
const paramName = param.name;
const paramValue = parseTopLevelType(
param.typeAnnotation.typeAnnotation,
parser,
types,
).type;
const type =
paramValue.type === 'TSTypeReference'
? parser.getTypeAnnotationName(paramValue)
: paramValue.type;
let returnType: CommandParamTypeAnnotation;
switch (type) {
case 'RootTag':
returnType = {
type: 'ReservedTypeAnnotation',
name: 'RootTag',
};
break;
case 'TSBooleanKeyword':
case 'Int32':
case 'Double':
case 'Float':
case 'TSStringKeyword':
returnType = getPrimitiveTypeAnnotation(type);
break;
case 'Array':
case 'ReadOnlyArray':
/* $FlowFixMe[invalid-compare] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/4oq3zi07. */
if (!paramValue.type === 'TSTypeReference') {
throw new Error(
'Array and ReadOnlyArray are TSTypeReference for array',
);
}
returnType = {
type: 'ArrayTypeAnnotation',
elementType: getCommandArrayElementTypeType(
paramValue.typeParameters.params[0],
parser,
),
};
break;
case 'TSArrayType':
returnType = {
type: 'ArrayTypeAnnotation',
elementType: getCommandArrayElementTypeType(
paramValue.elementType,
parser,
),
};
break;
default:
(type: mixed);
throw new Error(
`Unsupported param type for method "${name}", param "${paramName}". Found ${type}`,
);
}
return {
name: paramName,
optional: false,
typeAnnotation: returnType,
};
});
return {
name,
optional,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
params,
returnTypeAnnotation: {
type: 'VoidTypeAnnotation',
},
},
};
}
function getCommandArrayElementTypeType(
inputType: mixed,
parser: Parser,
): ComponentCommandArrayTypeAnnotation['elementType'] {
// TODO: T172453752 support more complex type annotation for array element
if (inputType == null || typeof inputType !== 'object') {
throw new Error(`Expected an object, received ${typeof inputType}`);
}
const type = inputType.type;
if (typeof type !== 'string') {
throw new Error('Command array element type must be a string');
}
// This is not a great solution. This generally means its a type alias to another type
// like an object or union. Ideally we'd encode that in the schema so the compat-check can
// validate those deeper objects for breaking changes and the generators can do something smarter.
// As of now, the generators just create ReadableMap or (const NSArray *) which are untyped
if (type === 'TSTypeReference') {
const name =
typeof inputType.typeName === 'object'
? parser.getTypeAnnotationName(inputType)
: null;
if (typeof name !== 'string') {
throw new Error('Expected TSTypeReference AST name to be a string');
}
try {
return getPrimitiveTypeAnnotation(name);
} catch (e) {
return {
type: 'MixedTypeAnnotation',
};
}
}
return getPrimitiveTypeAnnotation(type);
}
function buildCommandSchema(
property: EventTypeAST,
types: TypeDeclarationMap,
parser: Parser,
): NamedShape<CommandTypeAnnotation> {
if (property.type === 'TSPropertySignature') {
const topLevelType = parseTopLevelType(
property.typeAnnotation.typeAnnotation,
parser,
types,
);
const name = property.key.name;
const optional = property.optional || topLevelType.optional;
const parameters = topLevelType.type.parameters || topLevelType.type.params;
return buildCommandSchemaInternal(
name,
optional,
parameters,
types,
parser,
);
} else {
const name = property.key.name;
const optional = property.optional || false;
const parameters = property.parameters || property.params;
return buildCommandSchemaInternal(
name,
optional,
parameters,
types,
parser,
);
}
}
function getCommands(
commandTypeAST: $ReadOnlyArray<EventTypeAST>,
types: TypeDeclarationMap,
parser: Parser,
): $ReadOnlyArray<NamedShape<CommandTypeAnnotation>> {
return commandTypeAST
.filter(
property =>
property.type === 'TSPropertySignature' ||
property.type === 'TSMethodSignature',
)
.map(property => buildCommandSchema(property, types, parser))
.filter(Boolean);
}
module.exports = {
getCommands,
};

View File

@@ -0,0 +1,484 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {verifyPropNotAlreadyDefined} = require('../../parsers-commons');
const {
flattenIntersectionType,
parseTopLevelType,
} = require('../parseTopLevelType');
function getUnionOfLiterals(name, forArray, elementTypes, defaultValue, types) {
var _elementTypes$0$liter, _elementTypes$0$liter2;
elementTypes.reduce((lastType, currType) => {
const lastFlattenedType =
lastType && lastType.type === 'TSLiteralType'
? lastType.literal.type
: lastType.type;
const currFlattenedType =
currType.type === 'TSLiteralType' ? currType.literal.type : currType.type;
if (lastFlattenedType && currFlattenedType !== lastFlattenedType) {
throw new Error(`Mixed types are not supported (see "${name}")`);
}
return currType;
});
if (defaultValue === undefined) {
throw new Error(`A default enum value is required for "${name}"`);
}
const unionType = elementTypes[0].type;
if (
unionType === 'TSLiteralType' &&
((_elementTypes$0$liter = elementTypes[0].literal) === null ||
_elementTypes$0$liter === void 0
? void 0
: _elementTypes$0$liter.type) === 'StringLiteral'
) {
return {
type: 'StringEnumTypeAnnotation',
default: defaultValue,
options: elementTypes.map(option => option.literal.value),
};
} else if (
unionType === 'TSLiteralType' &&
((_elementTypes$0$liter2 = elementTypes[0].literal) === null ||
_elementTypes$0$liter2 === void 0
? void 0
: _elementTypes$0$liter2.type) === 'NumericLiteral'
) {
if (forArray) {
throw new Error(`Arrays of int enums are not supported (see: "${name}")`);
} else {
return {
type: 'Int32EnumTypeAnnotation',
default: defaultValue,
options: elementTypes.map(option => option.literal.value),
};
}
} else {
var _elementTypes$0$liter3;
throw new Error(
`Unsupported union type for "${name}", received "${unionType === 'TSLiteralType' ? ((_elementTypes$0$liter3 = elementTypes[0].literal) === null || _elementTypes$0$liter3 === void 0 ? void 0 : _elementTypes$0$liter3.type) : unionType}"`,
);
}
}
function detectArrayType(
name,
typeAnnotation,
defaultValue,
types,
parser,
buildSchema,
) {
// Covers: readonly T[]
if (
typeAnnotation.type === 'TSTypeOperator' &&
typeAnnotation.operator === 'readonly' &&
typeAnnotation.typeAnnotation.type === 'TSArrayType'
) {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeAnnotation.elementType,
defaultValue,
types,
parser,
buildSchema,
),
};
}
// Covers: T[]
if (typeAnnotation.type === 'TSArrayType') {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.elementType,
defaultValue,
types,
parser,
buildSchema,
),
};
}
// Covers: Array<T> and ReadonlyArray<T>
if (
typeAnnotation.type === 'TSTypeReference' &&
(parser.getTypeAnnotationName(typeAnnotation) === 'ReadonlyArray' ||
parser.getTypeAnnotationName(typeAnnotation) === 'Array')
) {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeParameters.params[0],
defaultValue,
types,
parser,
buildSchema,
),
};
}
return null;
}
function buildObjectType(rawProperties, types, parser, buildSchema) {
const flattenedProperties = flattenProperties(rawProperties, types, parser);
const properties = flattenedProperties
.map(prop => buildSchema(prop, types, parser))
.filter(Boolean);
return {
type: 'ObjectTypeAnnotation',
properties,
};
}
function getPrimitiveTypeAnnotation(type) {
switch (type) {
case 'Int32':
return {
type: 'Int32TypeAnnotation',
};
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
case 'Float':
return {
type: 'FloatTypeAnnotation',
};
case 'TSBooleanKeyword':
return {
type: 'BooleanTypeAnnotation',
};
case 'Stringish':
case 'TSStringKeyword':
return {
type: 'StringTypeAnnotation',
};
default:
throw new Error(`Unknown primitive type "${type}"`);
}
}
function getCommonTypeAnnotation(
name,
forArray,
type,
typeAnnotation,
defaultValue,
types,
parser,
buildSchema,
) {
switch (type) {
case 'TSTypeLiteral':
return buildObjectType(
typeAnnotation.members,
types,
parser,
buildSchema,
);
case 'TSInterfaceDeclaration':
return buildObjectType([typeAnnotation], types, parser, buildSchema);
case 'TSIntersectionType':
return buildObjectType(
flattenIntersectionType(typeAnnotation, parser, types),
types,
parser,
buildSchema,
);
case 'ImageSource':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageSourcePrimitive',
};
case 'ImageRequest':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageRequestPrimitive',
};
case 'ColorValue':
case 'ProcessedColorValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ColorPrimitive',
};
case 'PointValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'PointPrimitive',
};
case 'EdgeInsetsValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'EdgeInsetsPrimitive',
};
case 'DimensionValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'DimensionPrimitive',
};
case 'TSUnionType':
return getUnionOfLiterals(
name,
forArray,
typeAnnotation.types,
defaultValue,
types,
);
case 'Int32':
case 'Double':
case 'Float':
case 'TSBooleanKeyword':
case 'Stringish':
case 'TSStringKeyword':
return getPrimitiveTypeAnnotation(type);
case 'UnsafeMixed':
return {
type: 'MixedTypeAnnotation',
};
default:
return undefined;
}
}
function getTypeAnnotationForArray(
name,
typeAnnotation,
defaultValue,
types,
parser,
buildSchema,
) {
var _extractedTypeAnnotat;
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(typeAnnotation, parser, types);
if (topLevelType.defaultValue !== undefined) {
throw new Error(
'Nested optionals such as "ReadonlyArray<boolean | null | undefined>" are not supported, please declare optionals at the top level of value definitions as in "ReadonlyArray<boolean> | null | undefined"',
);
}
if (topLevelType.optional) {
throw new Error(
'Nested optionals such as "ReadonlyArray<boolean | null | undefined>" are not supported, please declare optionals at the top level of value definitions as in "ReadonlyArray<boolean> | null | undefined"',
);
}
const extractedTypeAnnotation = topLevelType.type;
const arrayType = detectArrayType(
name,
extractedTypeAnnotation,
defaultValue,
types,
parser,
buildSchema,
);
if (arrayType) {
if (arrayType.elementType.type !== 'ObjectTypeAnnotation') {
throw new Error(
`Only array of array of object is supported for "${name}".`,
);
}
return arrayType;
}
const type =
extractedTypeAnnotation.elementType === 'TSTypeReference'
? parser.getTypeAnnotationName(extractedTypeAnnotation.elementType)
: ((_extractedTypeAnnotat = extractedTypeAnnotation.elementType) ===
null || _extractedTypeAnnotat === void 0
? void 0
: _extractedTypeAnnotat.type) ||
parser.getTypeAnnotationName(extractedTypeAnnotation) ||
extractedTypeAnnotation.type;
const common = getCommonTypeAnnotation(
name,
true,
type,
extractedTypeAnnotation,
defaultValue,
types,
parser,
buildSchema,
);
if (common) {
return common;
}
switch (type) {
case 'TSNumberKeyword':
return {
type: 'FloatTypeAnnotation',
};
default:
type;
throw new Error(`Unknown prop type for "${name}": ${type}`);
}
}
function setDefaultValue(common, defaultValue) {
switch (common.type) {
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
common.default = defaultValue ? defaultValue : 0;
break;
case 'FloatTypeAnnotation':
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/5whu3i34. */
common.default =
defaultValue === null ? null : defaultValue ? defaultValue : 0;
break;
case 'BooleanTypeAnnotation':
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/5whu3i34. */
common.default = defaultValue === null ? null : !!defaultValue;
break;
case 'StringTypeAnnotation':
common.default = defaultValue === undefined ? null : defaultValue;
break;
}
}
function getTypeAnnotation(
name,
annotation,
defaultValue,
withNullDefault,
// Just to make `getTypeAnnotation` signature match with the one from Flow
types,
parser,
buildSchema,
) {
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(annotation, parser, types);
const typeAnnotation = topLevelType.type;
const arrayType = detectArrayType(
name,
typeAnnotation,
defaultValue,
types,
parser,
buildSchema,
);
if (arrayType) {
return arrayType;
}
const type =
typeAnnotation.type === 'TSTypeReference' ||
typeAnnotation.type === 'TSTypeAliasDeclaration'
? parser.getTypeAnnotationName(typeAnnotation)
: typeAnnotation.type;
const common = getCommonTypeAnnotation(
name,
false,
type,
typeAnnotation,
defaultValue,
types,
parser,
buildSchema,
);
if (common) {
setDefaultValue(common, defaultValue);
return common;
}
switch (type) {
case 'ColorArrayValue':
return {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'ReservedPropTypeAnnotation',
name: 'ColorPrimitive',
},
};
case 'TSNumberKeyword':
throw new Error(
`Cannot use "${type}" type annotation for "${name}": must use a specific numeric type like Int32, Double, or Float`,
);
case 'TSFunctionType':
throw new Error(
`Cannot use "${type}" type annotation for "${name}": must use a specific function type like BubblingEventHandler, or DirectEventHandler`,
);
default:
throw new Error(`Unknown prop type for "${name}": "${type}"`);
}
}
function getSchemaInfo(property, types, parser) {
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(
property.typeAnnotation.typeAnnotation,
parser,
types,
);
const name = property.key.name;
if (!property.optional && topLevelType.defaultValue !== undefined) {
throw new Error(
`key ${name} must be optional if used with WithDefault<> annotation`,
);
}
return {
name,
optional: property.optional || topLevelType.optional,
typeAnnotation: topLevelType.type,
defaultValue: topLevelType.defaultValue,
withNullDefault: false, // Just to make `getTypeAnnotation` signature match with the one from Flow
};
}
function flattenProperties(typeDefinition, types, parser) {
return typeDefinition
.map(property => {
if (property.type === 'TSPropertySignature') {
return property;
} else if (property.type === 'TSTypeReference') {
return flattenProperties(
parser.getProperties(property.typeName.name, types),
types,
parser,
);
} else if (
property.type === 'TSExpressionWithTypeArguments' ||
property.type === 'TSInterfaceHeritage'
) {
return flattenProperties(
parser.getProperties(property.expression.name, types),
types,
parser,
);
} else if (property.type === 'TSTypeLiteral') {
return flattenProperties(property.members, types, parser);
} else if (property.type === 'TSInterfaceDeclaration') {
return flattenProperties(
parser.getProperties(property.id.name, types),
types,
parser,
);
} else if (property.type === 'TSIntersectionType') {
return flattenProperties(property.types, types, parser);
} else {
throw new Error(
`${property.type} is not a supported object literal type.`,
);
}
})
.filter(Boolean)
.reduce((acc, item) => {
if (Array.isArray(item)) {
item.forEach(prop => {
verifyPropNotAlreadyDefined(acc, prop);
});
return acc.concat(item);
} else {
verifyPropNotAlreadyDefined(acc, item);
acc.push(item);
return acc;
}
}, [])
.filter(Boolean);
}
module.exports = {
getSchemaInfo,
getTypeAnnotation,
getPrimitiveTypeAnnotation,
flattenProperties,
};

View File

@@ -0,0 +1,539 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {BuildSchemaFN, Parser} from '../../parser';
import type {ASTNode, PropAST, TypeDeclarationMap} from '../../utils';
const {verifyPropNotAlreadyDefined} = require('../../parsers-commons');
const {
flattenIntersectionType,
parseTopLevelType,
} = require('../parseTopLevelType');
function getUnionOfLiterals(
name: string,
forArray: boolean,
elementTypes: $FlowFixMe[],
defaultValue: $FlowFixMe | void,
types: TypeDeclarationMap,
) {
elementTypes.reduce((lastType, currType) => {
const lastFlattenedType =
lastType && lastType.type === 'TSLiteralType'
? lastType.literal.type
: lastType.type;
const currFlattenedType =
currType.type === 'TSLiteralType' ? currType.literal.type : currType.type;
if (lastFlattenedType && currFlattenedType !== lastFlattenedType) {
throw new Error(`Mixed types are not supported (see "${name}")`);
}
return currType;
});
if (defaultValue === undefined) {
throw new Error(`A default enum value is required for "${name}"`);
}
const unionType = elementTypes[0].type;
if (
unionType === 'TSLiteralType' &&
elementTypes[0].literal?.type === 'StringLiteral'
) {
return {
type: 'StringEnumTypeAnnotation',
default: (defaultValue: string),
options: elementTypes.map(option => option.literal.value),
};
} else if (
unionType === 'TSLiteralType' &&
elementTypes[0].literal?.type === 'NumericLiteral'
) {
if (forArray) {
throw new Error(`Arrays of int enums are not supported (see: "${name}")`);
} else {
return {
type: 'Int32EnumTypeAnnotation',
default: (defaultValue: number),
options: elementTypes.map(option => option.literal.value),
};
}
} else {
throw new Error(
`Unsupported union type for "${name}", received "${
unionType === 'TSLiteralType'
? elementTypes[0].literal?.type
: unionType
}"`,
);
}
}
function detectArrayType<T>(
name: string,
typeAnnotation: $FlowFixMe | ASTNode,
defaultValue: $FlowFixMe | void,
types: TypeDeclarationMap,
parser: Parser,
buildSchema: BuildSchemaFN<T>,
): $FlowFixMe {
// Covers: readonly T[]
if (
typeAnnotation.type === 'TSTypeOperator' &&
typeAnnotation.operator === 'readonly' &&
typeAnnotation.typeAnnotation.type === 'TSArrayType'
) {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeAnnotation.elementType,
defaultValue,
types,
parser,
buildSchema,
),
};
}
// Covers: T[]
if (typeAnnotation.type === 'TSArrayType') {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.elementType,
defaultValue,
types,
parser,
buildSchema,
),
};
}
// Covers: Array<T> and ReadonlyArray<T>
if (
typeAnnotation.type === 'TSTypeReference' &&
(parser.getTypeAnnotationName(typeAnnotation) === 'ReadonlyArray' ||
parser.getTypeAnnotationName(typeAnnotation) === 'Array')
) {
return {
type: 'ArrayTypeAnnotation',
elementType: getTypeAnnotationForArray(
name,
typeAnnotation.typeParameters.params[0],
defaultValue,
types,
parser,
buildSchema,
),
};
}
return null;
}
function buildObjectType<T>(
rawProperties: Array<$FlowFixMe>,
types: TypeDeclarationMap,
parser: Parser,
buildSchema: BuildSchemaFN<T>,
): $FlowFixMe {
const flattenedProperties = flattenProperties(rawProperties, types, parser);
const properties = flattenedProperties
.map(prop => buildSchema(prop, types, parser))
.filter(Boolean);
return {
type: 'ObjectTypeAnnotation',
properties,
};
}
function getPrimitiveTypeAnnotation(type: string): $FlowFixMe {
switch (type) {
case 'Int32':
return {
type: 'Int32TypeAnnotation',
};
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
case 'Float':
return {
type: 'FloatTypeAnnotation',
};
case 'TSBooleanKeyword':
return {
type: 'BooleanTypeAnnotation',
};
case 'Stringish':
case 'TSStringKeyword':
return {
type: 'StringTypeAnnotation',
};
default:
throw new Error(`Unknown primitive type "${type}"`);
}
}
function getCommonTypeAnnotation<T>(
name: string,
forArray: boolean,
type: string,
typeAnnotation: $FlowFixMe,
defaultValue: $FlowFixMe | void,
types: TypeDeclarationMap,
parser: Parser,
buildSchema: BuildSchemaFN<T>,
): $FlowFixMe {
switch (type) {
case 'TSTypeLiteral':
return buildObjectType(
typeAnnotation.members,
types,
parser,
buildSchema,
);
case 'TSInterfaceDeclaration':
return buildObjectType([typeAnnotation], types, parser, buildSchema);
case 'TSIntersectionType':
return buildObjectType(
flattenIntersectionType(typeAnnotation, parser, types),
types,
parser,
buildSchema,
);
case 'ImageSource':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageSourcePrimitive',
};
case 'ImageRequest':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ImageRequestPrimitive',
};
case 'ColorValue':
case 'ProcessedColorValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'ColorPrimitive',
};
case 'PointValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'PointPrimitive',
};
case 'EdgeInsetsValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'EdgeInsetsPrimitive',
};
case 'DimensionValue':
return {
type: 'ReservedPropTypeAnnotation',
name: 'DimensionPrimitive',
};
case 'TSUnionType':
return getUnionOfLiterals(
name,
forArray,
typeAnnotation.types,
defaultValue,
types,
);
case 'Int32':
case 'Double':
case 'Float':
case 'TSBooleanKeyword':
case 'Stringish':
case 'TSStringKeyword':
return getPrimitiveTypeAnnotation(type);
case 'UnsafeMixed':
return {
type: 'MixedTypeAnnotation',
};
default:
return undefined;
}
}
function getTypeAnnotationForArray<T>(
name: string,
typeAnnotation: $FlowFixMe,
defaultValue: $FlowFixMe | void,
types: TypeDeclarationMap,
parser: Parser,
buildSchema: BuildSchemaFN<T>,
): $FlowFixMe {
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(typeAnnotation, parser, types);
if (topLevelType.defaultValue !== undefined) {
throw new Error(
'Nested optionals such as "ReadonlyArray<boolean | null | undefined>" are not supported, please declare optionals at the top level of value definitions as in "ReadonlyArray<boolean> | null | undefined"',
);
}
if (topLevelType.optional) {
throw new Error(
'Nested optionals such as "ReadonlyArray<boolean | null | undefined>" are not supported, please declare optionals at the top level of value definitions as in "ReadonlyArray<boolean> | null | undefined"',
);
}
const extractedTypeAnnotation = topLevelType.type;
const arrayType = detectArrayType(
name,
extractedTypeAnnotation,
defaultValue,
types,
parser,
buildSchema,
);
if (arrayType) {
if (arrayType.elementType.type !== 'ObjectTypeAnnotation') {
throw new Error(
`Only array of array of object is supported for "${name}".`,
);
}
return arrayType;
}
const type =
extractedTypeAnnotation.elementType === 'TSTypeReference'
? parser.getTypeAnnotationName(extractedTypeAnnotation.elementType)
: extractedTypeAnnotation.elementType?.type ||
parser.getTypeAnnotationName(extractedTypeAnnotation) ||
extractedTypeAnnotation.type;
const common = getCommonTypeAnnotation(
name,
true,
type,
extractedTypeAnnotation,
defaultValue,
types,
parser,
buildSchema,
);
if (common) {
return common;
}
switch (type) {
case 'TSNumberKeyword':
return {
type: 'FloatTypeAnnotation',
};
default:
(type: mixed);
throw new Error(`Unknown prop type for "${name}": ${type}`);
}
}
function setDefaultValue(
common: $FlowFixMe,
defaultValue: $FlowFixMe | void,
): void {
switch (common.type) {
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
common.default = ((defaultValue ? defaultValue : 0): number);
break;
case 'FloatTypeAnnotation':
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/5whu3i34. */
common.default = ((defaultValue === null
? null
: defaultValue
? defaultValue
: 0): number | null);
break;
case 'BooleanTypeAnnotation':
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/5whu3i34. */
common.default = defaultValue === null ? null : !!defaultValue;
break;
case 'StringTypeAnnotation':
common.default = ((defaultValue === undefined ? null : defaultValue):
| string
| null);
break;
}
}
function getTypeAnnotation<T>(
name: string,
annotation: $FlowFixMe | ASTNode,
defaultValue: $FlowFixMe | void,
withNullDefault: boolean, // Just to make `getTypeAnnotation` signature match with the one from Flow
types: TypeDeclarationMap,
parser: Parser,
buildSchema: BuildSchemaFN<T>,
): $FlowFixMe {
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(annotation, parser, types);
const typeAnnotation = topLevelType.type;
const arrayType = detectArrayType(
name,
typeAnnotation,
defaultValue,
types,
parser,
buildSchema,
);
if (arrayType) {
return arrayType;
}
const type =
typeAnnotation.type === 'TSTypeReference' ||
typeAnnotation.type === 'TSTypeAliasDeclaration'
? parser.getTypeAnnotationName(typeAnnotation)
: typeAnnotation.type;
const common = getCommonTypeAnnotation(
name,
false,
type,
typeAnnotation,
defaultValue,
types,
parser,
buildSchema,
);
if (common) {
setDefaultValue(common, defaultValue);
return common;
}
switch (type) {
case 'ColorArrayValue':
return {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'ReservedPropTypeAnnotation',
name: 'ColorPrimitive',
},
};
case 'TSNumberKeyword':
throw new Error(
`Cannot use "${type}" type annotation for "${name}": must use a specific numeric type like Int32, Double, or Float`,
);
case 'TSFunctionType':
throw new Error(
`Cannot use "${type}" type annotation for "${name}": must use a specific function type like BubblingEventHandler, or DirectEventHandler`,
);
default:
throw new Error(`Unknown prop type for "${name}": "${type}"`);
}
}
type SchemaInfo = {
name: string,
optional: boolean,
typeAnnotation: $FlowFixMe,
defaultValue: $FlowFixMe,
withNullDefault: boolean, // Just to make `getTypeAnnotation` signature match with the one from Flow
};
function getSchemaInfo(
property: PropAST,
types: TypeDeclarationMap,
parser: Parser,
): SchemaInfo {
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(
property.typeAnnotation.typeAnnotation,
parser,
types,
);
const name = property.key.name;
if (!property.optional && topLevelType.defaultValue !== undefined) {
throw new Error(
`key ${name} must be optional if used with WithDefault<> annotation`,
);
}
return {
name,
optional: property.optional || topLevelType.optional,
typeAnnotation: topLevelType.type,
defaultValue: topLevelType.defaultValue,
withNullDefault: false, // Just to make `getTypeAnnotation` signature match with the one from Flow
};
}
function flattenProperties(
typeDefinition: $ReadOnlyArray<PropAST>,
types: TypeDeclarationMap,
parser: Parser,
): $ReadOnlyArray<PropAST> {
return typeDefinition
.map(property => {
if (property.type === 'TSPropertySignature') {
return property;
} else if (property.type === 'TSTypeReference') {
return flattenProperties(
parser.getProperties(property.typeName.name, types),
types,
parser,
);
} else if (
property.type === 'TSExpressionWithTypeArguments' ||
property.type === 'TSInterfaceHeritage'
) {
return flattenProperties(
parser.getProperties(property.expression.name, types),
types,
parser,
);
} else if (property.type === 'TSTypeLiteral') {
return flattenProperties(property.members, types, parser);
} else if (property.type === 'TSInterfaceDeclaration') {
return flattenProperties(
parser.getProperties(property.id.name, types),
types,
parser,
);
} else if (property.type === 'TSIntersectionType') {
return flattenProperties(property.types, types, parser);
} else {
throw new Error(
`${property.type} is not a supported object literal type.`,
);
}
})
.filter(Boolean)
.reduce((acc: Array<PropAST>, item) => {
if (Array.isArray(item)) {
item.forEach((prop: PropAST) => {
verifyPropNotAlreadyDefined(acc, prop);
});
return acc.concat(item);
} else {
verifyPropNotAlreadyDefined(acc, item);
acc.push(item);
return acc;
}
}, [])
.filter(Boolean);
}
module.exports = {
getSchemaInfo,
getTypeAnnotation,
getPrimitiveTypeAnnotation,
flattenProperties,
};

View File

@@ -0,0 +1,253 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {
throwIfArgumentPropsAreNull,
throwIfBubblingTypeIsNull,
throwIfEventHasNoName,
} = require('../../error-utils');
const {
buildPropertiesForEvent,
emitBuildEventSchema,
getEventArgument,
handleEventHandler,
} = require('../../parsers-commons');
const {
emitBoolProp,
emitDoubleProp,
emitFloatProp,
emitInt32Prop,
emitMixedProp,
emitObjectProp,
emitStringProp,
emitUnionProp,
} = require('../../parsers-primitives');
const {parseTopLevelType} = require('../parseTopLevelType');
const {flattenProperties} = require('./componentsUtils');
function getPropertyType(
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
name,
optionalProperty,
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
annotation,
parser,
) {
const topLevelType = parseTopLevelType(annotation, parser);
const typeAnnotation = topLevelType.type;
const optional = optionalProperty || topLevelType.optional;
const type =
typeAnnotation.type === 'TSTypeReference'
? parser.getTypeAnnotationName(typeAnnotation)
: typeAnnotation.type;
switch (type) {
case 'TSBooleanKeyword':
return emitBoolProp(name, optional);
case 'TSStringKeyword':
return emitStringProp(name, optional);
case 'Int32':
return emitInt32Prop(name, optional);
case 'Double':
return emitDoubleProp(name, optional);
case 'Float':
return emitFloatProp(name, optional);
case 'TSTypeLiteral':
return emitObjectProp(
name,
optional,
parser,
typeAnnotation,
extractArrayElementType,
);
case 'TSUnionType':
return emitUnionProp(name, optional, parser, typeAnnotation);
case 'UnsafeMixed':
return emitMixedProp(name, optional);
case 'TSArrayType':
return {
name,
optional,
typeAnnotation: extractArrayElementType(typeAnnotation, name, parser),
};
default:
throw new Error(`Unable to determine event type for "${name}": ${type}`);
}
}
function extractArrayElementType(typeAnnotation, name, parser) {
const type = extractTypeFromTypeAnnotation(typeAnnotation, parser);
switch (type) {
case 'TSParenthesizedType':
return extractArrayElementType(
typeAnnotation.typeAnnotation,
name,
parser,
);
case 'TSBooleanKeyword':
return {
type: 'BooleanTypeAnnotation',
};
case 'TSStringKeyword':
return {
type: 'StringTypeAnnotation',
};
case 'Float':
return {
type: 'FloatTypeAnnotation',
};
case 'Int32':
return {
type: 'Int32TypeAnnotation',
};
case 'TSNumberKeyword':
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
case 'TSUnionType':
return {
type: 'StringLiteralUnionTypeAnnotation',
types: typeAnnotation.types.map(option => ({
type: 'StringLiteralTypeAnnotation',
value: parser.getLiteralValue(option),
})),
};
case 'TSTypeLiteral':
return {
type: 'ObjectTypeAnnotation',
properties: parser
.getObjectProperties(typeAnnotation)
.map(member =>
buildPropertiesForEvent(member, parser, getPropertyType),
),
};
case 'TSArrayType':
return {
type: 'ArrayTypeAnnotation',
elementType: extractArrayElementType(
typeAnnotation.elementType,
name,
parser,
),
};
default:
throw new Error(
`Unrecognized ${type} for Array ${name} in events.\n${JSON.stringify(typeAnnotation, null, 2)}`,
);
}
}
function extractTypeFromTypeAnnotation(typeAnnotation, parser) {
return typeAnnotation.type === 'TSTypeReference'
? parser.getTypeAnnotationName(typeAnnotation)
: typeAnnotation.type;
}
function findEventArgumentsAndType(
parser,
typeAnnotation,
types,
bubblingType,
paperName,
) {
if (typeAnnotation.type === 'TSInterfaceDeclaration') {
return {
argumentProps: flattenProperties([typeAnnotation], types, parser),
paperTopLevelNameDeprecated: paperName,
bubblingType,
};
}
if (typeAnnotation.type === 'TSTypeLiteral') {
return {
argumentProps: parser.getObjectProperties(typeAnnotation),
paperTopLevelNameDeprecated: paperName,
bubblingType,
};
}
throwIfEventHasNoName(typeAnnotation, parser);
const name = parser.getTypeAnnotationName(typeAnnotation);
if (name === 'Readonly') {
return findEventArgumentsAndType(
parser,
typeAnnotation.typeParameters.params[0],
types,
bubblingType,
paperName,
);
} else if (name === 'BubblingEventHandler' || name === 'DirectEventHandler') {
return handleEventHandler(
name,
typeAnnotation,
parser,
types,
findEventArgumentsAndType,
);
} else if (types[name]) {
let elementType = types[name];
if (elementType.type === 'TSTypeAliasDeclaration') {
elementType = elementType.typeAnnotation;
}
return findEventArgumentsAndType(
parser,
elementType,
types,
bubblingType,
paperName,
);
} else {
return {
argumentProps: null,
bubblingType: null,
paperTopLevelNameDeprecated: null,
};
}
}
// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser
function buildEventSchema(types, property, parser) {
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(
property.typeAnnotation.typeAnnotation,
parser,
types,
);
const name = property.key.name;
const typeAnnotation = topLevelType.type;
const optional = property.optional || topLevelType.optional;
const {argumentProps, bubblingType, paperTopLevelNameDeprecated} =
findEventArgumentsAndType(parser, typeAnnotation, types);
const nonNullableArgumentProps = throwIfArgumentPropsAreNull(
argumentProps,
name,
);
const nonNullableBubblingType = throwIfBubblingTypeIsNull(bubblingType, name);
const argument = getEventArgument(
nonNullableArgumentProps,
parser,
getPropertyType,
);
return emitBuildEventSchema(
paperTopLevelNameDeprecated,
name,
optional,
nonNullableBubblingType,
argument,
);
}
function getEvents(eventTypeAST, types, parser) {
return eventTypeAST
.map(property => buildEventSchema(types, property, parser))
.filter(Boolean);
}
module.exports = {
getEvents,
extractArrayElementType,
};

View File

@@ -0,0 +1,295 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
EventTypeAnnotation,
EventTypeShape,
NamedShape,
} from '../../../CodegenSchema.js';
import type {Parser} from '../../parser';
import type {TypeDeclarationMap} from '../../utils';
const {
throwIfArgumentPropsAreNull,
throwIfBubblingTypeIsNull,
throwIfEventHasNoName,
} = require('../../error-utils');
const {
buildPropertiesForEvent,
emitBuildEventSchema,
getEventArgument,
handleEventHandler,
} = require('../../parsers-commons');
const {
emitBoolProp,
emitDoubleProp,
emitFloatProp,
emitInt32Prop,
emitMixedProp,
emitObjectProp,
emitStringProp,
emitUnionProp,
} = require('../../parsers-primitives');
const {parseTopLevelType} = require('../parseTopLevelType');
const {flattenProperties} = require('./componentsUtils');
function getPropertyType(
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
name,
optionalProperty: boolean,
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
annotation,
parser: Parser,
): NamedShape<EventTypeAnnotation> {
const topLevelType = parseTopLevelType(annotation, parser);
const typeAnnotation = topLevelType.type;
const optional = optionalProperty || topLevelType.optional;
const type =
typeAnnotation.type === 'TSTypeReference'
? parser.getTypeAnnotationName(typeAnnotation)
: typeAnnotation.type;
switch (type) {
case 'TSBooleanKeyword':
return emitBoolProp(name, optional);
case 'TSStringKeyword':
return emitStringProp(name, optional);
case 'Int32':
return emitInt32Prop(name, optional);
case 'Double':
return emitDoubleProp(name, optional);
case 'Float':
return emitFloatProp(name, optional);
case 'TSTypeLiteral':
return emitObjectProp(
name,
optional,
parser,
typeAnnotation,
extractArrayElementType,
);
case 'TSUnionType':
return emitUnionProp(name, optional, parser, typeAnnotation);
case 'UnsafeMixed':
return emitMixedProp(name, optional);
case 'TSArrayType':
return {
name,
optional,
typeAnnotation: extractArrayElementType(typeAnnotation, name, parser),
};
default:
throw new Error(`Unable to determine event type for "${name}": ${type}`);
}
}
function extractArrayElementType(
typeAnnotation: $FlowFixMe,
name: string,
parser: Parser,
): EventTypeAnnotation {
const type = extractTypeFromTypeAnnotation(typeAnnotation, parser);
switch (type) {
case 'TSParenthesizedType':
return extractArrayElementType(
typeAnnotation.typeAnnotation,
name,
parser,
);
case 'TSBooleanKeyword':
return {type: 'BooleanTypeAnnotation'};
case 'TSStringKeyword':
return {type: 'StringTypeAnnotation'};
case 'Float':
return {
type: 'FloatTypeAnnotation',
};
case 'Int32':
return {
type: 'Int32TypeAnnotation',
};
case 'TSNumberKeyword':
case 'Double':
return {
type: 'DoubleTypeAnnotation',
};
case 'TSUnionType':
return {
type: 'StringLiteralUnionTypeAnnotation',
types: typeAnnotation.types.map(option => ({
type: 'StringLiteralTypeAnnotation',
value: parser.getLiteralValue(option),
})),
};
case 'TSTypeLiteral':
return {
type: 'ObjectTypeAnnotation',
properties: parser
.getObjectProperties(typeAnnotation)
.map(member =>
buildPropertiesForEvent(member, parser, getPropertyType),
),
};
case 'TSArrayType':
return {
type: 'ArrayTypeAnnotation',
elementType: extractArrayElementType(
typeAnnotation.elementType,
name,
parser,
),
};
default:
throw new Error(
`Unrecognized ${type} for Array ${name} in events.\n${JSON.stringify(
typeAnnotation,
null,
2,
)}`,
);
}
}
function extractTypeFromTypeAnnotation(
typeAnnotation: $FlowFixMe,
parser: Parser,
): string {
return typeAnnotation.type === 'TSTypeReference'
? parser.getTypeAnnotationName(typeAnnotation)
: typeAnnotation.type;
}
function findEventArgumentsAndType(
parser: Parser,
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
bubblingType: void | 'direct' | 'bubble',
paperName: ?$FlowFixMe,
): {
argumentProps: ?$ReadOnlyArray<$FlowFixMe>,
paperTopLevelNameDeprecated: ?$FlowFixMe,
bubblingType: ?'direct' | 'bubble',
} {
if (typeAnnotation.type === 'TSInterfaceDeclaration') {
return {
argumentProps: flattenProperties([typeAnnotation], types, parser),
paperTopLevelNameDeprecated: paperName,
bubblingType,
};
}
if (typeAnnotation.type === 'TSTypeLiteral') {
return {
argumentProps: parser.getObjectProperties(typeAnnotation),
paperTopLevelNameDeprecated: paperName,
bubblingType,
};
}
throwIfEventHasNoName(typeAnnotation, parser);
const name = parser.getTypeAnnotationName(typeAnnotation);
if (name === 'Readonly') {
return findEventArgumentsAndType(
parser,
typeAnnotation.typeParameters.params[0],
types,
bubblingType,
paperName,
);
} else if (name === 'BubblingEventHandler' || name === 'DirectEventHandler') {
return handleEventHandler(
name,
typeAnnotation,
parser,
types,
findEventArgumentsAndType,
);
} else if (types[name]) {
let elementType = types[name];
if (elementType.type === 'TSTypeAliasDeclaration') {
elementType = elementType.typeAnnotation;
}
return findEventArgumentsAndType(
parser,
elementType,
types,
bubblingType,
paperName,
);
} else {
return {
argumentProps: null,
bubblingType: null,
paperTopLevelNameDeprecated: null,
};
}
}
// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser
type EventTypeAST = Object;
function buildEventSchema(
types: TypeDeclarationMap,
property: EventTypeAST,
parser: Parser,
): ?EventTypeShape {
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(
property.typeAnnotation.typeAnnotation,
parser,
types,
);
const name = property.key.name;
const typeAnnotation = topLevelType.type;
const optional = property.optional || topLevelType.optional;
const {argumentProps, bubblingType, paperTopLevelNameDeprecated} =
findEventArgumentsAndType(parser, typeAnnotation, types);
const nonNullableArgumentProps = throwIfArgumentPropsAreNull(
argumentProps,
name,
);
const nonNullableBubblingType = throwIfBubblingTypeIsNull(bubblingType, name);
const argument = getEventArgument(
nonNullableArgumentProps,
parser,
getPropertyType,
);
return emitBuildEventSchema(
paperTopLevelNameDeprecated,
name,
optional,
nonNullableBubblingType,
argument,
);
}
function getEvents(
eventTypeAST: $ReadOnlyArray<EventTypeAST>,
types: TypeDeclarationMap,
parser: Parser,
): $ReadOnlyArray<EventTypeShape> {
return eventTypeAST
.map(property => buildEventSchema(types, property, parser))
.filter(Boolean);
}
module.exports = {
getEvents,
extractArrayElementType,
};

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {parseTopLevelType} = require('../parseTopLevelType');
function isEvent(typeAnnotation, parser) {
if (typeAnnotation.type !== 'TSTypeReference') {
return false;
}
const eventNames = new Set(['BubblingEventHandler', 'DirectEventHandler']);
return eventNames.has(parser.getTypeAnnotationName(typeAnnotation));
}
// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser
function categorizeProps(typeDefinition, types, events, parser) {
// find events
for (const prop of typeDefinition) {
if (prop.type === 'TSPropertySignature') {
const topLevelType = parseTopLevelType(
prop.typeAnnotation.typeAnnotation,
parser,
types,
);
if (isEvent(topLevelType.type, parser)) {
events.push(prop);
}
}
}
}
module.exports = {
categorizeProps,
};

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {Parser} from '../../parser';
import type {TypeDeclarationMap} from '../../utils';
const {parseTopLevelType} = require('../parseTopLevelType');
function isEvent(typeAnnotation: $FlowFixMe, parser: Parser): boolean {
if (typeAnnotation.type !== 'TSTypeReference') {
return false;
}
const eventNames = new Set(['BubblingEventHandler', 'DirectEventHandler']);
return eventNames.has(parser.getTypeAnnotationName(typeAnnotation));
}
// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser
type PropsAST = Object;
function categorizeProps(
typeDefinition: $ReadOnlyArray<PropsAST>,
types: TypeDeclarationMap,
events: Array<PropsAST>,
parser: Parser,
): void {
// find events
for (const prop of typeDefinition) {
if (prop.type === 'TSPropertySignature') {
const topLevelType = parseTopLevelType(
prop.typeAnnotation.typeAnnotation,
parser,
types,
);
if (isEvent(topLevelType.type, parser)) {
events.push(prop);
}
}
}
}
module.exports = {
categorizeProps,
};

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {
findComponentConfig,
getCommandProperties,
getOptions,
} = require('../../parsers-commons');
const {getCommands} = require('./commands');
const {getEvents} = require('./events');
const {categorizeProps} = require('./extends');
// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser
// $FlowFixMe[signature-verification-failure] TODO(T108222691): Use flow-types for @babel/parser
function buildComponentSchema(ast, parser) {
const {componentName, propsTypeName, optionsExpression} = findComponentConfig(
ast,
parser,
);
const types = parser.getTypes(ast);
const propProperties = parser.getProperties(propsTypeName, types);
const commandProperties = getCommandProperties(ast, parser);
const options = getOptions(optionsExpression);
const componentEventAsts = [];
categorizeProps(propProperties, types, componentEventAsts, parser);
const {props, extendsProps} = parser.getProps(propProperties, types);
const events = getEvents(componentEventAsts, types, parser);
const commands = getCommands(commandProperties, types, parser);
return {
filename: componentName,
componentName,
options,
extendsProps,
events,
props,
commands,
};
}
module.exports = {
buildComponentSchema,
};

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {Parser} from '../../parser';
import type {ComponentSchemaBuilderConfig} from '../../schema.js';
const {
findComponentConfig,
getCommandProperties,
getOptions,
} = require('../../parsers-commons');
const {getCommands} = require('./commands');
const {getEvents} = require('./events');
const {categorizeProps} = require('./extends');
// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser
type PropsAST = Object;
// $FlowFixMe[signature-verification-failure] TODO(T108222691): Use flow-types for @babel/parser
function buildComponentSchema(
ast: $FlowFixMe,
parser: Parser,
): ComponentSchemaBuilderConfig {
const {componentName, propsTypeName, optionsExpression} = findComponentConfig(
ast,
parser,
);
const types = parser.getTypes(ast);
const propProperties = parser.getProperties(propsTypeName, types);
const commandProperties = getCommandProperties(ast, parser);
const options = getOptions(optionsExpression);
const componentEventAsts: Array<PropsAST> = [];
categorizeProps(propProperties, types, componentEventAsts, parser);
const {props, extendsProps} = parser.getProps(propProperties, types);
const events = getEvents(componentEventAsts, types, parser);
const commands = getCommands(commandProperties, types, parser);
return {
filename: componentName,
componentName,
options,
extendsProps,
events,
props,
commands,
};
}
module.exports = {
buildComponentSchema,
};

View File

@@ -0,0 +1,427 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {
UnsupportedEnumDeclarationParserError,
UnsupportedGenericParserError,
UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError,
UnsupportedTypeAnnotationParserError,
} = require('../../errors');
const {parseObjectProperty} = require('../../parsers-commons');
const {
emitArrayType,
emitCommonTypes,
emitDictionary,
emitFunction,
emitNumberLiteral,
emitPromise,
emitRootTag,
emitStringLiteral,
emitUnion,
translateArrayTypeAnnotation,
typeAliasResolution,
typeEnumResolution,
} = require('../../parsers-primitives');
const {flattenProperties} = require('../components/componentsUtils');
const {flattenIntersectionType} = require('../parseTopLevelType');
function translateObjectTypeAnnotation(
hasteModuleName,
/**
* TODO(T108222691): Use flow-types for @babel/parser
*/
typeScriptTypeAnnotation,
nullable,
objectMembers,
typeResolutionStatus,
baseTypes,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
) {
// $FlowFixMe[missing-type-arg]
const properties = objectMembers
.map(property => {
return tryParse(() => {
return parseObjectProperty(
typeScriptTypeAnnotation,
property,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
translateTypeAnnotation,
parser,
);
});
})
.filter(Boolean);
let objectTypeAnnotation;
if (baseTypes.length === 0) {
objectTypeAnnotation = {
type: 'ObjectTypeAnnotation',
properties,
};
} else {
objectTypeAnnotation = {
type: 'ObjectTypeAnnotation',
properties,
baseTypes,
};
}
return typeAliasResolution(
typeResolutionStatus,
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
objectTypeAnnotation,
aliasMap,
nullable,
);
}
function translateTypeReferenceAnnotation(
typeName,
nullable,
typeAnnotation,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
) {
switch (typeName) {
case 'RootTag': {
return emitRootTag(nullable);
}
case 'Promise': {
return emitPromise(
hasteModuleName,
typeAnnotation,
parser,
nullable,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
);
}
case 'Array':
case 'ReadonlyArray': {
return emitArrayType(
hasteModuleName,
typeAnnotation,
parser,
types,
aliasMap,
enumMap,
cxxOnly,
nullable,
translateTypeAnnotation,
);
}
default: {
const commonType = emitCommonTypes(
hasteModuleName,
types,
typeAnnotation,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
parser,
);
if (!commonType) {
throw new UnsupportedGenericParserError(
hasteModuleName,
typeAnnotation,
parser,
);
}
return commonType;
}
}
}
function translateTypeAnnotation(
hasteModuleName,
/**
* TODO(T108222691): Use flow-types for @babel/parser
*/
typeScriptTypeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
) {
const {nullable, typeAnnotation, typeResolutionStatus} =
parser.getResolvedTypeAnnotation(typeScriptTypeAnnotation, types, parser);
const resolveTypeaAnnotationFn = parser.getResolveTypeAnnotationFN();
resolveTypeaAnnotationFn(typeScriptTypeAnnotation, types, parser);
switch (typeAnnotation.type) {
case 'TSArrayType': {
return translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
enumMap,
cxxOnly,
'Array',
typeAnnotation.elementType,
nullable,
translateTypeAnnotation,
parser,
);
}
case 'TSTypeOperator': {
if (
typeAnnotation.operator === 'readonly' &&
typeAnnotation.typeAnnotation.type === 'TSArrayType'
) {
return translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
enumMap,
cxxOnly,
'ReadonlyArray',
typeAnnotation.typeAnnotation.elementType,
nullable,
translateTypeAnnotation,
parser,
);
} else {
throw new UnsupportedGenericParserError(
hasteModuleName,
typeAnnotation,
parser,
);
}
}
case 'TSTypeReference': {
return translateTypeReferenceAnnotation(
parser.getTypeAnnotationName(typeAnnotation),
nullable,
typeAnnotation,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
case 'TSInterfaceDeclaration': {
var _typeAnnotation$exten;
const baseTypes = (
(_typeAnnotation$exten = typeAnnotation.extends) !== null &&
_typeAnnotation$exten !== void 0
? _typeAnnotation$exten
: []
).map(extend => extend.expression.name);
for (const baseType of baseTypes) {
// ensure base types exist and appear in aliasMap
translateTypeAnnotation(
hasteModuleName,
{
type: 'TSTypeReference',
typeName: {
type: 'Identifier',
name: baseType,
},
},
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
return translateObjectTypeAnnotation(
hasteModuleName,
typeScriptTypeAnnotation,
nullable,
flattenProperties([typeAnnotation], types, parser),
typeResolutionStatus,
baseTypes,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
case 'TSIntersectionType': {
return translateObjectTypeAnnotation(
hasteModuleName,
typeScriptTypeAnnotation,
nullable,
flattenProperties(
flattenIntersectionType(typeAnnotation, parser, types),
types,
parser,
),
typeResolutionStatus,
[],
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
case 'TSTypeLiteral': {
// if there is TSIndexSignature, then it is a dictionary
if (typeAnnotation.members) {
const indexSignatures = typeAnnotation.members.filter(
member => member.type === 'TSIndexSignature',
);
const properties = typeAnnotation.members.filter(
member => member.type === 'TSPropertySignature',
);
if (indexSignatures.length > 0 && properties.length > 0) {
throw new UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
);
}
if (indexSignatures.length > 0) {
// check the property type to prevent developers from using unsupported types
// the return value from `translateTypeAnnotation` is unused
const propertyType = indexSignatures[0].typeAnnotation;
const valueType = translateTypeAnnotation(
hasteModuleName,
propertyType,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
// no need to do further checking
return emitDictionary(nullable, valueType);
}
}
return translateObjectTypeAnnotation(
hasteModuleName,
typeScriptTypeAnnotation,
nullable,
typeAnnotation.members,
typeResolutionStatus,
[],
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
case 'TSEnumDeclaration': {
if (
typeAnnotation.members.some(
m =>
m.initializer &&
m.initializer.type === 'NumericLiteral' &&
!Number.isInteger(m.initializer.value),
)
) {
throw new UnsupportedEnumDeclarationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
return typeEnumResolution(
typeAnnotation,
typeResolutionStatus,
nullable,
hasteModuleName,
enumMap,
parser,
);
}
case 'TSFunctionType': {
return emitFunction(
nullable,
hasteModuleName,
typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
parser,
);
}
case 'TSUnionType': {
return emitUnion(nullable, hasteModuleName, typeAnnotation, parser);
}
case 'TSLiteralType': {
const literal = typeAnnotation.literal;
switch (literal.type) {
case 'StringLiteral': {
return emitStringLiteral(nullable, literal.value);
}
case 'NumericLiteral': {
return emitNumberLiteral(nullable, literal.value);
}
default: {
throw new UnsupportedTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
}
}
default: {
const commonType = emitCommonTypes(
hasteModuleName,
types,
typeAnnotation,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
parser,
);
if (!commonType) {
throw new UnsupportedTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
return commonType;
}
}
}
module.exports = {
typeScriptTranslateTypeAnnotation: translateTypeAnnotation,
};

View File

@@ -0,0 +1,448 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
NamedShape,
NativeModuleAliasMap,
NativeModuleBaseTypeAnnotation,
NativeModuleEnumMap,
NativeModuleTypeAnnotation,
Nullable,
} from '../../../CodegenSchema';
import type {Parser} from '../../parser';
import type {
ParserErrorCapturer,
TypeDeclarationMap,
TypeResolutionStatus,
} from '../../utils';
const {
UnsupportedEnumDeclarationParserError,
UnsupportedGenericParserError,
UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError,
UnsupportedTypeAnnotationParserError,
} = require('../../errors');
const {parseObjectProperty} = require('../../parsers-commons');
const {
emitArrayType,
emitCommonTypes,
emitDictionary,
emitFunction,
emitNumberLiteral,
emitPromise,
emitRootTag,
emitStringLiteral,
emitUnion,
translateArrayTypeAnnotation,
typeAliasResolution,
typeEnumResolution,
} = require('../../parsers-primitives');
const {flattenProperties} = require('../components/componentsUtils');
const {flattenIntersectionType} = require('../parseTopLevelType');
function translateObjectTypeAnnotation(
hasteModuleName: string,
/**
* TODO(T108222691): Use flow-types for @babel/parser
*/
typeScriptTypeAnnotation: $FlowFixMe,
nullable: boolean,
objectMembers: $ReadOnlyArray<$FlowFixMe>,
typeResolutionStatus: TypeResolutionStatus,
baseTypes: $ReadOnlyArray<string>,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
parser: Parser,
): Nullable<NativeModuleTypeAnnotation> {
// $FlowFixMe[missing-type-arg]
const properties = objectMembers
.map<?NamedShape<Nullable<NativeModuleBaseTypeAnnotation>>>(property => {
return tryParse(() => {
return parseObjectProperty(
typeScriptTypeAnnotation,
property,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
translateTypeAnnotation,
parser,
);
});
})
.filter(Boolean);
let objectTypeAnnotation;
if (baseTypes.length === 0) {
objectTypeAnnotation = {
type: 'ObjectTypeAnnotation',
properties,
};
} else {
objectTypeAnnotation = {
type: 'ObjectTypeAnnotation',
properties,
baseTypes,
};
}
return typeAliasResolution(
typeResolutionStatus,
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
objectTypeAnnotation,
aliasMap,
nullable,
);
}
function translateTypeReferenceAnnotation(
typeName: string,
nullable: boolean,
typeAnnotation: $FlowFixMe,
hasteModuleName: string,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
parser: Parser,
): Nullable<NativeModuleTypeAnnotation> {
switch (typeName) {
case 'RootTag': {
return emitRootTag(nullable);
}
case 'Promise': {
return emitPromise(
hasteModuleName,
typeAnnotation,
parser,
nullable,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
);
}
case 'Array':
case 'ReadonlyArray': {
return emitArrayType(
hasteModuleName,
typeAnnotation,
parser,
types,
aliasMap,
enumMap,
cxxOnly,
nullable,
translateTypeAnnotation,
);
}
default: {
const commonType = emitCommonTypes(
hasteModuleName,
types,
typeAnnotation,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
parser,
);
if (!commonType) {
throw new UnsupportedGenericParserError(
hasteModuleName,
typeAnnotation,
parser,
);
}
return commonType;
}
}
}
function translateTypeAnnotation(
hasteModuleName: string,
/**
* TODO(T108222691): Use flow-types for @babel/parser
*/
typeScriptTypeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
parser: Parser,
): Nullable<NativeModuleTypeAnnotation> {
const {nullable, typeAnnotation, typeResolutionStatus} =
parser.getResolvedTypeAnnotation(typeScriptTypeAnnotation, types, parser);
const resolveTypeaAnnotationFn = parser.getResolveTypeAnnotationFN();
resolveTypeaAnnotationFn(typeScriptTypeAnnotation, types, parser);
switch (typeAnnotation.type) {
case 'TSArrayType': {
return translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
enumMap,
cxxOnly,
'Array',
typeAnnotation.elementType,
nullable,
translateTypeAnnotation,
parser,
);
}
case 'TSTypeOperator': {
if (
typeAnnotation.operator === 'readonly' &&
typeAnnotation.typeAnnotation.type === 'TSArrayType'
) {
return translateArrayTypeAnnotation(
hasteModuleName,
types,
aliasMap,
enumMap,
cxxOnly,
'ReadonlyArray',
typeAnnotation.typeAnnotation.elementType,
nullable,
translateTypeAnnotation,
parser,
);
} else {
throw new UnsupportedGenericParserError(
hasteModuleName,
typeAnnotation,
parser,
);
}
}
case 'TSTypeReference': {
return translateTypeReferenceAnnotation(
parser.getTypeAnnotationName(typeAnnotation),
nullable,
typeAnnotation,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
case 'TSInterfaceDeclaration': {
const baseTypes = (typeAnnotation.extends ?? []).map(
extend => extend.expression.name,
);
for (const baseType of baseTypes) {
// ensure base types exist and appear in aliasMap
translateTypeAnnotation(
hasteModuleName,
{
type: 'TSTypeReference',
typeName: {type: 'Identifier', name: baseType},
},
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
return translateObjectTypeAnnotation(
hasteModuleName,
typeScriptTypeAnnotation,
nullable,
flattenProperties([typeAnnotation], types, parser),
typeResolutionStatus,
baseTypes,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
case 'TSIntersectionType': {
return translateObjectTypeAnnotation(
hasteModuleName,
typeScriptTypeAnnotation,
nullable,
flattenProperties(
flattenIntersectionType(typeAnnotation, parser, types),
types,
parser,
),
typeResolutionStatus,
[],
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
case 'TSTypeLiteral': {
// if there is TSIndexSignature, then it is a dictionary
if (typeAnnotation.members) {
const indexSignatures = typeAnnotation.members.filter(
member => member.type === 'TSIndexSignature',
);
const properties = typeAnnotation.members.filter(
member => member.type === 'TSPropertySignature',
);
if (indexSignatures.length > 0 && properties.length > 0) {
throw new UnsupportedObjectPropertyWithIndexerTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
);
}
if (indexSignatures.length > 0) {
// check the property type to prevent developers from using unsupported types
// the return value from `translateTypeAnnotation` is unused
const propertyType = indexSignatures[0].typeAnnotation;
const valueType = translateTypeAnnotation(
hasteModuleName,
propertyType,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
// no need to do further checking
return emitDictionary(nullable, valueType);
}
}
return translateObjectTypeAnnotation(
hasteModuleName,
typeScriptTypeAnnotation,
nullable,
typeAnnotation.members,
typeResolutionStatus,
[],
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
parser,
);
}
case 'TSEnumDeclaration': {
if (
typeAnnotation.members.some(
m =>
m.initializer &&
m.initializer.type === 'NumericLiteral' &&
!Number.isInteger(m.initializer.value),
)
) {
throw new UnsupportedEnumDeclarationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
return typeEnumResolution(
typeAnnotation,
typeResolutionStatus,
nullable,
hasteModuleName,
enumMap,
parser,
);
}
case 'TSFunctionType': {
return emitFunction(
nullable,
hasteModuleName,
typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
translateTypeAnnotation,
parser,
);
}
case 'TSUnionType': {
return emitUnion(nullable, hasteModuleName, typeAnnotation, parser);
}
case 'TSLiteralType': {
const literal = typeAnnotation.literal;
switch (literal.type) {
case 'StringLiteral': {
return emitStringLiteral(nullable, literal.value);
}
case 'NumericLiteral': {
return emitNumberLiteral(nullable, literal.value);
}
default: {
throw new UnsupportedTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
}
}
default: {
const commonType = emitCommonTypes(
hasteModuleName,
types,
typeAnnotation,
aliasMap,
enumMap,
tryParse,
cxxOnly,
nullable,
parser,
);
if (!commonType) {
throw new UnsupportedTypeAnnotationParserError(
hasteModuleName,
typeAnnotation,
parser.language(),
);
}
return commonType;
}
}
}
module.exports = {
typeScriptTranslateTypeAnnotation: translateTypeAnnotation,
};

View File

@@ -0,0 +1,219 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function getValueFromTypes(value, types) {
switch (value.type) {
case 'TSTypeReference':
if (types[value.typeName.name]) {
return getValueFromTypes(types[value.typeName.name], types);
} else {
return value;
}
case 'TSTypeAliasDeclaration':
return getValueFromTypes(value.typeAnnotation, types);
default:
return value;
}
}
function isNull(t) {
return t.type === 'TSNullKeyword' || t.type === 'TSUndefinedKeyword';
}
function isNullOrVoid(t) {
return isNull(t) || t.type === 'TSVoidKeyword';
}
function couldBeNumericLiteral(type) {
return type === 'Literal' || type === 'NumericLiteral';
}
function couldBeSimpleLiteral(type) {
return (
couldBeNumericLiteral(type) ||
type === 'StringLiteral' ||
type === 'BooleanLiteral'
);
}
function evaluateLiteral(literalNode) {
const valueType = literalNode.type;
if (valueType === 'TSLiteralType') {
const literal = literalNode.literal;
if (couldBeSimpleLiteral(literal.type)) {
if (
typeof literal.value === 'string' ||
typeof literal.value === 'number' ||
typeof literal.value === 'boolean'
) {
return literal.value;
}
} else if (
literal.type === 'UnaryExpression' &&
literal.operator === '-' &&
couldBeNumericLiteral(literal.argument.type) &&
typeof literal.argument.value === 'number'
) {
return -literal.argument.value;
}
} else if (isNull(literalNode)) {
return null;
}
throw new Error(
'The default value in WithDefault must be string, number, boolean or null .',
);
}
function handleUnionAndParen(type, result, parser, knownTypes) {
switch (type.type) {
case 'TSParenthesizedType': {
handleUnionAndParen(type.typeAnnotation, result, parser, knownTypes);
break;
}
case 'TSUnionType': {
// the order is important
// result.optional must be set first
for (const t of type.types) {
if (isNullOrVoid(t)) {
result.optional = true;
}
}
for (const t of type.types) {
if (!isNullOrVoid(t)) {
handleUnionAndParen(t, result, parser, knownTypes);
}
}
break;
}
case 'TSTypeReference':
if (type.typeName.name === 'Readonly') {
handleUnionAndParen(
type.typeParameters.params[0],
result,
parser,
knownTypes,
);
} else if (parser.getTypeAnnotationName(type) === 'WithDefault') {
if (result.optional) {
throw new Error(
'WithDefault<> is optional and does not need to be marked as optional. Please remove the union of undefined and/or null',
);
}
if (type.typeParameters.params.length !== 2) {
throw new Error(
'WithDefault requires two parameters: type and default value.',
);
}
if (result.defaultValue !== undefined) {
throw new Error(
'Multiple WithDefault is not allowed nested or in a union type.',
);
}
result.optional = true;
result.defaultValue = evaluateLiteral(type.typeParameters.params[1]);
handleUnionAndParen(
type.typeParameters.params[0],
result,
parser,
knownTypes,
);
} else if (!knownTypes) {
result.unions.push(type);
} else {
const resolvedType = getValueFromTypes(type, knownTypes);
if (
resolvedType.type === 'TSTypeReference' &&
resolvedType.typeName.name === type.typeName.name
) {
result.unions.push(type);
} else {
handleUnionAndParen(resolvedType, result, parser, knownTypes);
}
}
break;
default:
result.unions.push(type);
}
}
function parseTopLevelType(type, parser, knownTypes) {
let result = {
unions: [],
optional: false,
};
handleUnionAndParen(type, result, parser, knownTypes);
if (result.unions.length === 0) {
throw new Error('Union type could not be just null or undefined.');
} else if (result.unions.length === 1) {
return {
type: result.unions[0],
optional: result.optional,
defaultValue: result.defaultValue,
};
} else {
return {
type: {
type: 'TSUnionType',
types: result.unions,
},
optional: result.optional,
defaultValue: result.defaultValue,
};
}
}
function handleIntersectionAndParen(type, result, parser, knownTypes) {
switch (type.type) {
case 'TSParenthesizedType': {
handleIntersectionAndParen(
type.typeAnnotation,
result,
parser,
knownTypes,
);
break;
}
case 'TSIntersectionType': {
for (const t of type.types) {
handleIntersectionAndParen(t, result, parser, knownTypes);
}
break;
}
case 'TSTypeReference':
if (type.typeName.name === 'Readonly') {
handleIntersectionAndParen(
type.typeParameters.params[0],
result,
parser,
knownTypes,
);
} else if (parser.getTypeAnnotationName(type) === 'WithDefault') {
throw new Error('WithDefault<> is now allowed in intersection types.');
} else if (!knownTypes) {
result.push(type);
} else {
const resolvedType = getValueFromTypes(type, knownTypes);
if (
resolvedType.type === 'TSTypeReference' &&
resolvedType.typeName.name === type.typeName.name
) {
result.push(type);
} else {
handleIntersectionAndParen(resolvedType, result, parser, knownTypes);
}
}
break;
default:
result.push(type);
}
}
function flattenIntersectionType(type, parser, knownTypes) {
const result = [];
handleIntersectionAndParen(type, result, parser, knownTypes);
return result;
}
module.exports = {
parseTopLevelType,
flattenIntersectionType,
};

View File

@@ -0,0 +1,264 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {Parser} from '../parser';
import type {TypeDeclarationMap} from '../utils';
export type LegalDefaultValues = string | number | boolean | null;
type TopLevelTypeInternal = {
unions: Array<$FlowFixMe>,
optional: boolean,
defaultValue?: LegalDefaultValues,
};
export type TopLevelType = {
type: $FlowFixMe,
optional: boolean,
defaultValue?: LegalDefaultValues,
};
function getValueFromTypes(
value: $FlowFixMe,
types: TypeDeclarationMap,
): $FlowFixMe {
switch (value.type) {
case 'TSTypeReference':
if (types[value.typeName.name]) {
return getValueFromTypes(types[value.typeName.name], types);
} else {
return value;
}
case 'TSTypeAliasDeclaration':
return getValueFromTypes(value.typeAnnotation, types);
default:
return value;
}
}
function isNull(t: $FlowFixMe) {
return t.type === 'TSNullKeyword' || t.type === 'TSUndefinedKeyword';
}
function isNullOrVoid(t: $FlowFixMe) {
return isNull(t) || t.type === 'TSVoidKeyword';
}
function couldBeNumericLiteral(type: string) {
return type === 'Literal' || type === 'NumericLiteral';
}
function couldBeSimpleLiteral(type: string) {
return (
couldBeNumericLiteral(type) ||
type === 'StringLiteral' ||
type === 'BooleanLiteral'
);
}
function evaluateLiteral(
literalNode: $FlowFixMe,
): string | number | boolean | null {
const valueType = literalNode.type;
if (valueType === 'TSLiteralType') {
const literal = literalNode.literal;
if (couldBeSimpleLiteral(literal.type)) {
if (
typeof literal.value === 'string' ||
typeof literal.value === 'number' ||
typeof literal.value === 'boolean'
) {
return literal.value;
}
} else if (
literal.type === 'UnaryExpression' &&
literal.operator === '-' &&
couldBeNumericLiteral(literal.argument.type) &&
typeof literal.argument.value === 'number'
) {
return -literal.argument.value;
}
} else if (isNull(literalNode)) {
return null;
}
throw new Error(
'The default value in WithDefault must be string, number, boolean or null .',
);
}
function handleUnionAndParen(
type: $FlowFixMe,
result: TopLevelTypeInternal,
parser: Parser,
knownTypes?: TypeDeclarationMap,
): void {
switch (type.type) {
case 'TSParenthesizedType': {
handleUnionAndParen(type.typeAnnotation, result, parser, knownTypes);
break;
}
case 'TSUnionType': {
// the order is important
// result.optional must be set first
for (const t of type.types) {
if (isNullOrVoid(t)) {
result.optional = true;
}
}
for (const t of type.types) {
if (!isNullOrVoid(t)) {
handleUnionAndParen(t, result, parser, knownTypes);
}
}
break;
}
case 'TSTypeReference':
if (type.typeName.name === 'Readonly') {
handleUnionAndParen(
type.typeParameters.params[0],
result,
parser,
knownTypes,
);
} else if (parser.getTypeAnnotationName(type) === 'WithDefault') {
if (result.optional) {
throw new Error(
'WithDefault<> is optional and does not need to be marked as optional. Please remove the union of undefined and/or null',
);
}
if (type.typeParameters.params.length !== 2) {
throw new Error(
'WithDefault requires two parameters: type and default value.',
);
}
if (result.defaultValue !== undefined) {
throw new Error(
'Multiple WithDefault is not allowed nested or in a union type.',
);
}
result.optional = true;
result.defaultValue = evaluateLiteral(type.typeParameters.params[1]);
handleUnionAndParen(
type.typeParameters.params[0],
result,
parser,
knownTypes,
);
} else if (!knownTypes) {
result.unions.push(type);
} else {
const resolvedType = getValueFromTypes(type, knownTypes);
if (
resolvedType.type === 'TSTypeReference' &&
resolvedType.typeName.name === type.typeName.name
) {
result.unions.push(type);
} else {
handleUnionAndParen(resolvedType, result, parser, knownTypes);
}
}
break;
default:
result.unions.push(type);
}
}
function parseTopLevelType(
type: $FlowFixMe,
parser: Parser,
knownTypes?: TypeDeclarationMap,
): TopLevelType {
let result: TopLevelTypeInternal = {unions: [], optional: false};
handleUnionAndParen(type, result, parser, knownTypes);
if (result.unions.length === 0) {
throw new Error('Union type could not be just null or undefined.');
} else if (result.unions.length === 1) {
return {
type: result.unions[0],
optional: result.optional,
defaultValue: result.defaultValue,
};
} else {
return {
type: {type: 'TSUnionType', types: result.unions},
optional: result.optional,
defaultValue: result.defaultValue,
};
}
}
function handleIntersectionAndParen(
type: $FlowFixMe,
result: Array<$FlowFixMe>,
parser: Parser,
knownTypes?: TypeDeclarationMap,
): void {
switch (type.type) {
case 'TSParenthesizedType': {
handleIntersectionAndParen(
type.typeAnnotation,
result,
parser,
knownTypes,
);
break;
}
case 'TSIntersectionType': {
for (const t of type.types) {
handleIntersectionAndParen(t, result, parser, knownTypes);
}
break;
}
case 'TSTypeReference':
if (type.typeName.name === 'Readonly') {
handleIntersectionAndParen(
type.typeParameters.params[0],
result,
parser,
knownTypes,
);
} else if (parser.getTypeAnnotationName(type) === 'WithDefault') {
throw new Error('WithDefault<> is now allowed in intersection types.');
} else if (!knownTypes) {
result.push(type);
} else {
const resolvedType = getValueFromTypes(type, knownTypes);
if (
resolvedType.type === 'TSTypeReference' &&
resolvedType.typeName.name === type.typeName.name
) {
result.push(type);
} else {
handleIntersectionAndParen(resolvedType, result, parser, knownTypes);
}
}
break;
default:
result.push(type);
}
}
function flattenIntersectionType(
type: $FlowFixMe,
parser: Parser,
knownTypes?: TypeDeclarationMap,
): Array<$FlowFixMe> {
const result: Array<$FlowFixMe> = [];
handleIntersectionAndParen(type, result, parser, knownTypes);
return result;
}
module.exports = {
parseTopLevelType,
flattenIntersectionType,
};

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { Parser } from '../parser';
import type { SchemaType } from '../../CodegenSchema';
import type { ParserType } from '../errors';
export declare class TypeScriptParser implements Parser {
language(): ParserType;
parseFile(filename: string): SchemaType;
parseString(contents: string, filename?: string): SchemaType;
parseModuleFixture(filename: string): SchemaType;
}

View File

@@ -0,0 +1,620 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function _defineProperty(e, r, t) {
return (
(r = _toPropertyKey(r)) in e
? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0,
})
: (e[r] = t),
e
);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, 'string');
return 'symbol' == typeof i ? i : i + '';
}
function _toPrimitive(t, r) {
if ('object' != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || 'default');
if ('object' != typeof i) return i;
throw new TypeError('@@toPrimitive must return a primitive value.');
}
return ('string' === r ? String : Number)(t);
}
const {
UnsupportedObjectPropertyTypeAnnotationParserError,
} = require('../errors');
const {
buildModuleSchema,
buildPropSchema,
buildSchema,
extendsForProp,
handleGenericTypeAnnotation,
} = require('../parsers-commons.js');
const {Visitor} = require('../parsers-primitives');
const {wrapComponentSchema} = require('../schema.js');
const {buildComponentSchema} = require('./components');
const {
flattenProperties,
getSchemaInfo,
getTypeAnnotation,
} = require('./components/componentsUtils');
const {typeScriptTranslateTypeAnnotation} = require('./modules');
const {parseTopLevelType} = require('./parseTopLevelType');
// $FlowFixMe[untyped-import] Use flow-types for @babel/parser
const babelParser = require('@babel/parser');
const fs = require('fs');
const invariant = require('invariant');
class TypeScriptParser {
constructor() {
_defineProperty(
this,
'typeParameterInstantiation',
'TSTypeParameterInstantiation',
);
_defineProperty(this, 'typeAlias', 'TSTypeAliasDeclaration');
_defineProperty(this, 'enumDeclaration', 'TSEnumDeclaration');
_defineProperty(this, 'interfaceDeclaration', 'TSInterfaceDeclaration');
_defineProperty(this, 'nullLiteralTypeAnnotation', 'TSNullKeyword');
_defineProperty(
this,
'undefinedLiteralTypeAnnotation',
'TSUndefinedKeyword',
);
}
isProperty(property) {
return property.type === 'TSPropertySignature';
}
getKeyName(property, hasteModuleName) {
if (!this.isProperty(property)) {
throw new UnsupportedObjectPropertyTypeAnnotationParserError(
hasteModuleName,
property,
property.type,
this.language(),
);
}
return property.key.name;
}
language() {
return 'TypeScript';
}
getTypeAnnotationName(typeAnnotation) {
var _typeAnnotation$typeN, _typeAnnotation$typeN2;
if (
(typeAnnotation === null ||
typeAnnotation === void 0 ||
(_typeAnnotation$typeN = typeAnnotation.typeName) === null ||
_typeAnnotation$typeN === void 0
? void 0
: _typeAnnotation$typeN.type) === 'TSQualifiedName'
) {
return typeAnnotation.typeName.right.name;
}
return typeAnnotation === null ||
typeAnnotation === void 0 ||
(_typeAnnotation$typeN2 = typeAnnotation.typeName) === null ||
_typeAnnotation$typeN2 === void 0
? void 0
: _typeAnnotation$typeN2.name;
}
checkIfInvalidModule(typeArguments) {
return (
typeArguments.type !== 'TSTypeParameterInstantiation' ||
typeArguments.params.length !== 1 ||
typeArguments.params[0].type !== 'TSTypeReference' ||
typeArguments.params[0].typeName.name !== 'Spec'
);
}
remapUnionTypeAnnotationMemberNames(membersTypes) {
const remapLiteral = item => {
return item.literal
? item.literal.type
.replace('NumericLiteral', 'NumberTypeAnnotation')
.replace('StringLiteral', 'StringTypeAnnotation')
: 'ObjectTypeAnnotation';
};
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return [...new Set(membersTypes.map(remapLiteral))];
}
getStringLiteralUnionTypeAnnotationStringLiterals(membersTypes) {
return membersTypes.map(item => item.literal.value);
}
parseFile(filename) {
const contents = fs.readFileSync(filename, 'utf8');
return this.parseString(contents, filename);
}
parseString(contents, filename) {
return buildSchema(
contents,
filename,
wrapComponentSchema,
buildComponentSchema,
buildModuleSchema,
Visitor,
this,
typeScriptTranslateTypeAnnotation,
);
}
parseModuleFixture(filename) {
const contents = fs.readFileSync(filename, 'utf8');
return this.parseString(contents, 'path/NativeSampleTurboModule.ts');
}
getAst(contents, filename) {
return babelParser.parse(contents, {
sourceType: 'module',
plugins: ['typescript'],
}).program;
}
getFunctionTypeAnnotationParameters(functionTypeAnnotation) {
return functionTypeAnnotation.parameters;
}
getFunctionNameFromParameter(parameter) {
return parameter.typeAnnotation;
}
getParameterName(parameter) {
return parameter.name;
}
getParameterTypeAnnotation(parameter) {
return parameter.typeAnnotation.typeAnnotation;
}
getFunctionTypeAnnotationReturnType(functionTypeAnnotation) {
return functionTypeAnnotation.typeAnnotation.typeAnnotation;
}
parseEnumMembersType(typeAnnotation) {
var _typeAnnotation$membe;
const enumInitializer =
(_typeAnnotation$membe = typeAnnotation.members[0]) === null ||
_typeAnnotation$membe === void 0
? void 0
: _typeAnnotation$membe.initializer;
const enumInitializerType =
enumInitializer === null || enumInitializer === void 0
? void 0
: enumInitializer.type;
let enumMembersType = null;
if (!enumInitializerType) {
return 'StringTypeAnnotation';
}
switch (enumInitializerType) {
case 'StringLiteral':
enumMembersType = 'StringTypeAnnotation';
break;
case 'NumericLiteral':
enumMembersType = 'NumberTypeAnnotation';
break;
case 'UnaryExpression':
if (enumInitializer.operator === '-') {
enumMembersType = 'NumberTypeAnnotation';
}
break;
default:
enumMembersType = null;
}
if (!enumMembersType) {
throw new Error(
'Enum values must be either blank, number, or string values.',
);
}
return enumMembersType;
}
validateEnumMembersSupported(typeAnnotation, enumMembersType) {
if (!typeAnnotation.members || typeAnnotation.members.length === 0) {
throw new Error('Enums should have at least one member.');
}
const enumInitializerType =
enumMembersType === 'StringTypeAnnotation'
? 'StringLiteral'
: enumMembersType === 'NumberTypeAnnotation'
? 'NumericLiteral'
: null;
typeAnnotation.members.forEach(member => {
var _member$initializer,
_member$initializer2,
_member$initializer3,
_member$initializer4;
const isNegative =
((_member$initializer = member.initializer) === null ||
_member$initializer === void 0
? void 0
: _member$initializer.type) === 'UnaryExpression' &&
((_member$initializer2 = member.initializer) === null ||
_member$initializer2 === void 0
? void 0
: _member$initializer2.operator) === '-';
const initializerType = isNegative
? (_member$initializer3 = member.initializer) === null ||
_member$initializer3 === void 0 ||
(_member$initializer3 = _member$initializer3.argument) === null ||
_member$initializer3 === void 0
? void 0
: _member$initializer3.type
: (_member$initializer4 = member.initializer) === null ||
_member$initializer4 === void 0
? void 0
: _member$initializer4.type;
if (
(initializerType !== null && initializerType !== void 0
? initializerType
: 'StringLiteral') !== enumInitializerType
) {
throw new Error(
'Enum values can not be mixed. They all must be either blank, number, or string values.',
);
}
});
}
parseEnumMembers(typeAnnotation) {
return typeAnnotation.members.map(member => {
var _member$initializer5,
_member$initializer6,
_member$initializer7,
_member$initializer8,
_member$initializer9,
_member$initializer0;
const value =
((_member$initializer5 = member.initializer) === null ||
_member$initializer5 === void 0
? void 0
: _member$initializer5.operator) === '-'
? {
type: 'NumberLiteralTypeAnnotation',
value:
-1 *
((_member$initializer6 = member.initializer) === null ||
_member$initializer6 === void 0 ||
(_member$initializer6 = _member$initializer6.argument) ===
null ||
_member$initializer6 === void 0
? void 0
: _member$initializer6.value),
}
: typeof ((_member$initializer7 = member.initializer) === null ||
_member$initializer7 === void 0
? void 0
: _member$initializer7.value) === 'number'
? {
type: 'NumberLiteralTypeAnnotation',
value:
(_member$initializer8 = member.initializer) === null ||
_member$initializer8 === void 0
? void 0
: _member$initializer8.value,
}
: typeof ((_member$initializer9 = member.initializer) === null ||
_member$initializer9 === void 0
? void 0
: _member$initializer9.value) === 'string'
? {
type: 'StringLiteralTypeAnnotation',
value:
(_member$initializer0 = member.initializer) === null ||
_member$initializer0 === void 0
? void 0
: _member$initializer0.value,
}
: {
type: 'StringLiteralTypeAnnotation',
value: member.id.name,
};
return {
name: member.id.name,
value,
};
});
}
isModuleInterface(node) {
var _node$extends;
return (
node.type === 'TSInterfaceDeclaration' &&
((_node$extends = node.extends) === null || _node$extends === void 0
? void 0
: _node$extends.length) === 1 &&
node.extends[0].type === 'TSExpressionWithTypeArguments' &&
node.extends[0].expression.name === 'TurboModule'
);
}
isGenericTypeAnnotation(type) {
return type === 'TSTypeReference';
}
extractAnnotatedElement(typeAnnotation, types) {
return types[typeAnnotation.typeParameters.params[0].typeName.name];
}
/**
* TODO(T108222691): Use flow-types for @babel/parser
*/
getTypes(ast) {
return ast.body.reduce((types, node) => {
switch (node.type) {
case 'ExportNamedDeclaration': {
if (node.declaration) {
switch (node.declaration.type) {
case 'TSTypeAliasDeclaration':
case 'TSInterfaceDeclaration':
case 'TSEnumDeclaration': {
types[node.declaration.id.name] = node.declaration;
break;
}
}
}
break;
}
case 'TSTypeAliasDeclaration':
case 'TSInterfaceDeclaration':
case 'TSEnumDeclaration': {
types[node.id.name] = node;
break;
}
}
return types;
}, {});
}
callExpressionTypeParameters(callExpression) {
return callExpression.typeParameters || null;
}
computePartialProperties(
properties,
hasteModuleName,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
) {
return properties.map(prop => {
return {
name: prop.key.name,
optional: true,
typeAnnotation: typeScriptTranslateTypeAnnotation(
hasteModuleName,
prop.typeAnnotation.typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
this,
),
};
});
}
functionTypeAnnotation(propertyValueType) {
return (
propertyValueType === 'TSFunctionType' ||
propertyValueType === 'TSMethodSignature'
);
}
getTypeArgumentParamsFromDeclaration(declaration) {
return declaration.typeParameters.params;
}
// This FlowFixMe is supposed to refer to typeArgumentParams and funcArgumentParams of generated AST.
getNativeComponentType(typeArgumentParams, funcArgumentParams) {
return {
propsTypeName: typeArgumentParams[0].typeName.name,
componentName: funcArgumentParams[0].value,
};
}
getAnnotatedElementProperties(annotatedElement) {
return annotatedElement.typeAnnotation.members;
}
bodyProperties(typeAlias) {
return typeAlias.body.body;
}
convertKeywordToTypeAnnotation(keyword) {
switch (keyword) {
case 'TSArrayType':
return 'ArrayTypeAnnotation';
case 'TSBooleanKeyword':
return 'BooleanTypeAnnotation';
case 'TSNumberKeyword':
return 'NumberTypeAnnotation';
case 'TSVoidKeyword':
return 'VoidTypeAnnotation';
case 'TSStringKeyword':
return 'StringTypeAnnotation';
case 'TSTypeLiteral':
return 'ObjectTypeAnnotation';
case 'TSUnknownKeyword':
return 'MixedTypeAnnotation';
}
return keyword;
}
argumentForProp(prop) {
return prop.expression;
}
nameForArgument(prop) {
return prop.expression.name;
}
isOptionalProperty(property) {
return property.optional || false;
}
getGetSchemaInfoFN() {
return getSchemaInfo;
}
getTypeAnnotationFromProperty(property) {
return property.typeAnnotation.typeAnnotation;
}
getGetTypeAnnotationFN() {
return getTypeAnnotation;
}
getResolvedTypeAnnotation(
// TODO(T108222691): Use flow-types for @babel/parser
typeAnnotation,
types,
parser,
) {
invariant(
typeAnnotation != null,
'resolveTypeAnnotation(): typeAnnotation cannot be null',
);
let node =
typeAnnotation.type === 'TSTypeAnnotation'
? typeAnnotation.typeAnnotation
: typeAnnotation;
let nullable = false;
let typeResolutionStatus = {
successful: false,
};
for (;;) {
const topLevelType = parseTopLevelType(node, parser);
nullable = nullable || topLevelType.optional;
node = topLevelType.type;
if (node.type !== 'TSTypeReference') {
break;
}
const typeAnnotationName = this.getTypeAnnotationName(node);
const resolvedTypeAnnotation = types[typeAnnotationName];
if (resolvedTypeAnnotation == null) {
break;
}
const {typeAnnotation: typeAnnotationNode, typeResolutionStatus: status} =
handleGenericTypeAnnotation(node, resolvedTypeAnnotation, this);
typeResolutionStatus = status;
node = typeAnnotationNode;
}
return {
nullable: nullable,
typeAnnotation: node,
typeResolutionStatus,
};
}
getResolveTypeAnnotationFN() {
return (typeAnnotation, types, parser) => {
return this.getResolvedTypeAnnotation(typeAnnotation, types, parser);
};
}
isEvent(typeAnnotation) {
if (typeAnnotation.type !== 'TSTypeReference') {
return false;
}
const eventNames = new Set(['BubblingEventHandler', 'DirectEventHandler']);
return eventNames.has(this.getTypeAnnotationName(typeAnnotation));
}
isProp(name, typeAnnotation) {
if (typeAnnotation.type !== 'TSTypeReference') {
return true;
}
const isStyle =
name === 'style' &&
typeAnnotation.type === 'GenericTypeAnnotation' &&
this.getTypeAnnotationName(typeAnnotation) === 'ViewStyleProp';
return !isStyle;
}
getProps(typeDefinition, types) {
const extendsProps = [];
const componentPropAsts = [];
const remaining = [];
for (const prop of typeDefinition) {
// find extends
if (prop.type === 'TSExpressionWithTypeArguments') {
const extend = extendsForProp(prop, types, this);
if (extend) {
extendsProps.push(extend);
continue;
}
}
remaining.push(prop);
}
// find events and props
for (const prop of flattenProperties(remaining, types, this)) {
const topLevelType = parseTopLevelType(
prop.typeAnnotation.typeAnnotation,
this,
types,
);
if (
prop.type === 'TSPropertySignature' &&
!this.isEvent(topLevelType.type) &&
this.isProp(prop.key.name, prop)
) {
componentPropAsts.push(prop);
}
}
return {
props: componentPropAsts
.map(property => buildPropSchema(property, types, this))
.filter(Boolean),
extendsProps,
};
}
getProperties(typeName, types) {
const alias = types[typeName];
if (!alias) {
throw new Error(
`Failed to find definition for "${typeName}", please check that you have a valid codegen typescript file`,
);
}
const aliasKind =
alias.type === 'TSInterfaceDeclaration' ? 'interface' : 'type';
try {
if (aliasKind === 'interface') {
var _alias$extends;
return [
...((_alias$extends = alias.extends) !== null &&
_alias$extends !== void 0
? _alias$extends
: []),
...alias.body.body,
];
}
return (
alias.typeAnnotation.members ||
alias.typeAnnotation.typeParameters.params[0].members ||
alias.typeAnnotation.typeParameters.params
);
} catch (e) {
throw new Error(
`Failed to find ${aliasKind} definition for "${typeName}", please check that you have a valid codegen typescript file`,
);
}
}
nextNodeForTypeAlias(typeAnnotation) {
return typeAnnotation.typeAnnotation;
}
nextNodeForEnum(typeAnnotation) {
return typeAnnotation;
}
genericTypeAnnotationErrorMessage(typeAnnotation) {
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}.`;
}
extractTypeFromTypeAnnotation(typeAnnotation) {
return typeAnnotation.type === 'TSTypeReference'
? typeAnnotation.typeName.name
: typeAnnotation.type;
}
getObjectProperties(typeAnnotation) {
return typeAnnotation.members;
}
getLiteralValue(option) {
return option.literal.value;
}
getPaperTopLevelNameDeprecated(typeAnnotation) {
return typeAnnotation.typeParameters.params.length > 1
? typeAnnotation.typeParameters.params[1].literal.value
: null;
}
}
module.exports = {
TypeScriptParser,
};

View File

@@ -0,0 +1,635 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
ExtendsPropsShape,
NamedShape,
NativeModuleAliasMap,
NativeModuleEnumMap,
NativeModuleEnumMember,
NativeModuleEnumMemberType,
NativeModuleParamTypeAnnotation,
Nullable,
PropTypeAnnotation,
SchemaType,
UnionTypeAnnotationMemberType,
} from '../../CodegenSchema';
import type {ParserType} from '../errors';
import type {
GetSchemaInfoFN,
GetTypeAnnotationFN,
Parser,
ResolveTypeAnnotationFN,
} from '../parser';
import type {
ParserErrorCapturer,
PropAST,
TypeDeclarationMap,
TypeResolutionStatus,
} from '../utils';
const {
UnsupportedObjectPropertyTypeAnnotationParserError,
} = require('../errors');
const {
buildModuleSchema,
buildPropSchema,
buildSchema,
extendsForProp,
handleGenericTypeAnnotation,
} = require('../parsers-commons.js');
const {Visitor} = require('../parsers-primitives');
const {wrapComponentSchema} = require('../schema.js');
const {buildComponentSchema} = require('./components');
const {
flattenProperties,
getSchemaInfo,
getTypeAnnotation,
} = require('./components/componentsUtils');
const {typeScriptTranslateTypeAnnotation} = require('./modules');
const {parseTopLevelType} = require('./parseTopLevelType');
// $FlowFixMe[untyped-import] Use flow-types for @babel/parser
const babelParser = require('@babel/parser');
const fs = require('fs');
const invariant = require('invariant');
class TypeScriptParser implements Parser {
typeParameterInstantiation: string = 'TSTypeParameterInstantiation';
typeAlias: string = 'TSTypeAliasDeclaration';
enumDeclaration: string = 'TSEnumDeclaration';
interfaceDeclaration: string = 'TSInterfaceDeclaration';
nullLiteralTypeAnnotation: string = 'TSNullKeyword';
undefinedLiteralTypeAnnotation: string = 'TSUndefinedKeyword';
isProperty(property: $FlowFixMe): boolean {
return property.type === 'TSPropertySignature';
}
getKeyName(property: $FlowFixMe, hasteModuleName: string): string {
if (!this.isProperty(property)) {
throw new UnsupportedObjectPropertyTypeAnnotationParserError(
hasteModuleName,
property,
property.type,
this.language(),
);
}
return property.key.name;
}
language(): ParserType {
return 'TypeScript';
}
getTypeAnnotationName(typeAnnotation: $FlowFixMe): string {
if (typeAnnotation?.typeName?.type === 'TSQualifiedName') {
return typeAnnotation.typeName.right.name;
}
return typeAnnotation?.typeName?.name;
}
checkIfInvalidModule(typeArguments: $FlowFixMe): boolean {
return (
typeArguments.type !== 'TSTypeParameterInstantiation' ||
typeArguments.params.length !== 1 ||
typeArguments.params[0].type !== 'TSTypeReference' ||
typeArguments.params[0].typeName.name !== 'Spec'
);
}
remapUnionTypeAnnotationMemberNames(
membersTypes: Array<$FlowFixMe>,
): Array<UnionTypeAnnotationMemberType> {
const remapLiteral = (item: $FlowFixMe) => {
return item.literal
? item.literal.type
.replace('NumericLiteral', 'NumberTypeAnnotation')
.replace('StringLiteral', 'StringTypeAnnotation')
: 'ObjectTypeAnnotation';
};
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return [...new Set(membersTypes.map(remapLiteral))];
}
getStringLiteralUnionTypeAnnotationStringLiterals(
membersTypes: Array<$FlowFixMe>,
): Array<string> {
return membersTypes.map((item: $FlowFixMe) => item.literal.value);
}
parseFile(filename: string): SchemaType {
const contents = fs.readFileSync(filename, 'utf8');
return this.parseString(contents, filename);
}
parseString(contents: string, filename: ?string): SchemaType {
return buildSchema(
contents,
filename,
wrapComponentSchema,
buildComponentSchema,
buildModuleSchema,
Visitor,
this,
typeScriptTranslateTypeAnnotation,
);
}
parseModuleFixture(filename: string): SchemaType {
const contents = fs.readFileSync(filename, 'utf8');
return this.parseString(contents, 'path/NativeSampleTurboModule.ts');
}
getAst(contents: string, filename?: ?string): $FlowFixMe {
return babelParser.parse(contents, {
sourceType: 'module',
plugins: ['typescript'],
}).program;
}
getFunctionTypeAnnotationParameters(
functionTypeAnnotation: $FlowFixMe,
): $ReadOnlyArray<$FlowFixMe> {
return functionTypeAnnotation.parameters;
}
getFunctionNameFromParameter(
parameter: NamedShape<Nullable<NativeModuleParamTypeAnnotation>>,
): $FlowFixMe {
return parameter.typeAnnotation;
}
getParameterName(parameter: $FlowFixMe): string {
return parameter.name;
}
getParameterTypeAnnotation(parameter: $FlowFixMe): $FlowFixMe {
return parameter.typeAnnotation.typeAnnotation;
}
getFunctionTypeAnnotationReturnType(
functionTypeAnnotation: $FlowFixMe,
): $FlowFixMe {
return functionTypeAnnotation.typeAnnotation.typeAnnotation;
}
parseEnumMembersType(typeAnnotation: $FlowFixMe): NativeModuleEnumMemberType {
const enumInitializer = typeAnnotation.members[0]?.initializer;
const enumInitializerType = enumInitializer?.type;
let enumMembersType: ?NativeModuleEnumMemberType = null;
if (!enumInitializerType) {
return 'StringTypeAnnotation';
}
switch (enumInitializerType) {
case 'StringLiteral':
enumMembersType = 'StringTypeAnnotation';
break;
case 'NumericLiteral':
enumMembersType = 'NumberTypeAnnotation';
break;
case 'UnaryExpression':
if (enumInitializer.operator === '-') {
enumMembersType = 'NumberTypeAnnotation';
}
break;
default:
enumMembersType = null;
}
if (!enumMembersType) {
throw new Error(
'Enum values must be either blank, number, or string values.',
);
}
return enumMembersType;
}
validateEnumMembersSupported(
typeAnnotation: $FlowFixMe,
enumMembersType: NativeModuleEnumMemberType,
): void {
if (!typeAnnotation.members || typeAnnotation.members.length === 0) {
throw new Error('Enums should have at least one member.');
}
const enumInitializerType =
enumMembersType === 'StringTypeAnnotation'
? 'StringLiteral'
: enumMembersType === 'NumberTypeAnnotation'
? 'NumericLiteral'
: null;
typeAnnotation.members.forEach(member => {
const isNegative =
member.initializer?.type === 'UnaryExpression' &&
member.initializer?.operator === '-';
const initializerType = isNegative
? member.initializer?.argument?.type
: member.initializer?.type;
if ((initializerType ?? 'StringLiteral') !== enumInitializerType) {
throw new Error(
'Enum values can not be mixed. They all must be either blank, number, or string values.',
);
}
});
}
parseEnumMembers(
typeAnnotation: $FlowFixMe,
): $ReadOnlyArray<NativeModuleEnumMember> {
return typeAnnotation.members.map(member => {
const value =
member.initializer?.operator === '-'
? {
type: 'NumberLiteralTypeAnnotation',
value: -1 * member.initializer?.argument?.value,
}
: typeof member.initializer?.value === 'number'
? {
type: 'NumberLiteralTypeAnnotation',
value: member.initializer?.value,
}
: typeof member.initializer?.value === 'string'
? {
type: 'StringLiteralTypeAnnotation',
value: member.initializer?.value,
}
: {
type: 'StringLiteralTypeAnnotation',
value: member.id.name,
};
return {
name: member.id.name,
value,
};
});
}
isModuleInterface(node: $FlowFixMe): boolean {
return (
node.type === 'TSInterfaceDeclaration' &&
node.extends?.length === 1 &&
node.extends[0].type === 'TSExpressionWithTypeArguments' &&
node.extends[0].expression.name === 'TurboModule'
);
}
isGenericTypeAnnotation(type: $FlowFixMe): boolean {
return type === 'TSTypeReference';
}
extractAnnotatedElement(
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
): $FlowFixMe {
return types[typeAnnotation.typeParameters.params[0].typeName.name];
}
/**
* TODO(T108222691): Use flow-types for @babel/parser
*/
getTypes(ast: $FlowFixMe): TypeDeclarationMap {
return ast.body.reduce((types, node) => {
switch (node.type) {
case 'ExportNamedDeclaration': {
if (node.declaration) {
switch (node.declaration.type) {
case 'TSTypeAliasDeclaration':
case 'TSInterfaceDeclaration':
case 'TSEnumDeclaration': {
types[node.declaration.id.name] = node.declaration;
break;
}
}
}
break;
}
case 'TSTypeAliasDeclaration':
case 'TSInterfaceDeclaration':
case 'TSEnumDeclaration': {
types[node.id.name] = node;
break;
}
}
return types;
}, {});
}
callExpressionTypeParameters(callExpression: $FlowFixMe): $FlowFixMe | null {
return callExpression.typeParameters || null;
}
computePartialProperties(
properties: Array<$FlowFixMe>,
hasteModuleName: string,
types: TypeDeclarationMap,
aliasMap: {...NativeModuleAliasMap},
enumMap: {...NativeModuleEnumMap},
tryParse: ParserErrorCapturer,
cxxOnly: boolean,
): Array<$FlowFixMe> {
return properties.map(prop => {
return {
name: prop.key.name,
optional: true,
typeAnnotation: typeScriptTranslateTypeAnnotation(
hasteModuleName,
prop.typeAnnotation.typeAnnotation,
types,
aliasMap,
enumMap,
tryParse,
cxxOnly,
this,
),
};
});
}
functionTypeAnnotation(propertyValueType: string): boolean {
return (
propertyValueType === 'TSFunctionType' ||
propertyValueType === 'TSMethodSignature'
);
}
getTypeArgumentParamsFromDeclaration(declaration: $FlowFixMe): $FlowFixMe {
return declaration.typeParameters.params;
}
// This FlowFixMe is supposed to refer to typeArgumentParams and funcArgumentParams of generated AST.
getNativeComponentType(
typeArgumentParams: $FlowFixMe,
funcArgumentParams: $FlowFixMe,
): {[string]: string} {
return {
propsTypeName: typeArgumentParams[0].typeName.name,
componentName: funcArgumentParams[0].value,
};
}
getAnnotatedElementProperties(annotatedElement: $FlowFixMe): $FlowFixMe {
return annotatedElement.typeAnnotation.members;
}
bodyProperties(typeAlias: TypeDeclarationMap): $ReadOnlyArray<$FlowFixMe> {
return typeAlias.body.body;
}
convertKeywordToTypeAnnotation(keyword: string): string {
switch (keyword) {
case 'TSArrayType':
return 'ArrayTypeAnnotation';
case 'TSBooleanKeyword':
return 'BooleanTypeAnnotation';
case 'TSNumberKeyword':
return 'NumberTypeAnnotation';
case 'TSVoidKeyword':
return 'VoidTypeAnnotation';
case 'TSStringKeyword':
return 'StringTypeAnnotation';
case 'TSTypeLiteral':
return 'ObjectTypeAnnotation';
case 'TSUnknownKeyword':
return 'MixedTypeAnnotation';
}
return keyword;
}
argumentForProp(prop: PropAST): $FlowFixMe {
return prop.expression;
}
nameForArgument(prop: PropAST): $FlowFixMe {
return prop.expression.name;
}
isOptionalProperty(property: $FlowFixMe): boolean {
return property.optional || false;
}
getGetSchemaInfoFN(): GetSchemaInfoFN {
return getSchemaInfo;
}
getTypeAnnotationFromProperty(property: PropAST): $FlowFixMe {
return property.typeAnnotation.typeAnnotation;
}
getGetTypeAnnotationFN(): GetTypeAnnotationFN {
return getTypeAnnotation;
}
getResolvedTypeAnnotation(
// TODO(T108222691): Use flow-types for @babel/parser
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
parser: Parser,
): {
nullable: boolean,
typeAnnotation: $FlowFixMe,
typeResolutionStatus: TypeResolutionStatus,
} {
invariant(
typeAnnotation != null,
'resolveTypeAnnotation(): typeAnnotation cannot be null',
);
let node =
typeAnnotation.type === 'TSTypeAnnotation'
? typeAnnotation.typeAnnotation
: typeAnnotation;
let nullable = false;
let typeResolutionStatus: TypeResolutionStatus = {
successful: false,
};
for (;;) {
const topLevelType = parseTopLevelType(node, parser);
nullable = nullable || topLevelType.optional;
node = topLevelType.type;
if (node.type !== 'TSTypeReference') {
break;
}
const typeAnnotationName = this.getTypeAnnotationName(node);
const resolvedTypeAnnotation = types[typeAnnotationName];
if (resolvedTypeAnnotation == null) {
break;
}
const {typeAnnotation: typeAnnotationNode, typeResolutionStatus: status} =
handleGenericTypeAnnotation(node, resolvedTypeAnnotation, this);
typeResolutionStatus = status;
node = typeAnnotationNode;
}
return {
nullable: nullable,
typeAnnotation: node,
typeResolutionStatus,
};
}
getResolveTypeAnnotationFN(): ResolveTypeAnnotationFN {
return (
typeAnnotation: $FlowFixMe,
types: TypeDeclarationMap,
parser: Parser,
) => {
return this.getResolvedTypeAnnotation(typeAnnotation, types, parser);
};
}
isEvent(typeAnnotation: $FlowFixMe): boolean {
if (typeAnnotation.type !== 'TSTypeReference') {
return false;
}
const eventNames = new Set(['BubblingEventHandler', 'DirectEventHandler']);
return eventNames.has(this.getTypeAnnotationName(typeAnnotation));
}
isProp(name: string, typeAnnotation: $FlowFixMe): boolean {
if (typeAnnotation.type !== 'TSTypeReference') {
return true;
}
const isStyle =
name === 'style' &&
typeAnnotation.type === 'GenericTypeAnnotation' &&
this.getTypeAnnotationName(typeAnnotation) === 'ViewStyleProp';
return !isStyle;
}
getProps(
typeDefinition: $ReadOnlyArray<PropAST>,
types: TypeDeclarationMap,
): {
props: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
extendsProps: $ReadOnlyArray<ExtendsPropsShape>,
} {
const extendsProps: Array<ExtendsPropsShape> = [];
const componentPropAsts: Array<PropAST> = [];
const remaining: Array<PropAST> = [];
for (const prop of typeDefinition) {
// find extends
if (prop.type === 'TSExpressionWithTypeArguments') {
const extend = extendsForProp(prop, types, this);
if (extend) {
extendsProps.push(extend);
continue;
}
}
remaining.push(prop);
}
// find events and props
for (const prop of flattenProperties(remaining, types, this)) {
const topLevelType = parseTopLevelType(
prop.typeAnnotation.typeAnnotation,
this,
types,
);
if (
prop.type === 'TSPropertySignature' &&
!this.isEvent(topLevelType.type) &&
this.isProp(prop.key.name, prop)
) {
componentPropAsts.push(prop);
}
}
return {
props: componentPropAsts
.map(property => buildPropSchema(property, types, this))
.filter(Boolean),
extendsProps,
};
}
getProperties(typeName: string, types: TypeDeclarationMap): $FlowFixMe {
const alias = types[typeName];
if (!alias) {
throw new Error(
`Failed to find definition for "${typeName}", please check that you have a valid codegen typescript file`,
);
}
const aliasKind =
alias.type === 'TSInterfaceDeclaration' ? 'interface' : 'type';
try {
if (aliasKind === 'interface') {
return [...(alias.extends ?? []), ...alias.body.body];
}
return (
alias.typeAnnotation.members ||
alias.typeAnnotation.typeParameters.params[0].members ||
alias.typeAnnotation.typeParameters.params
);
} catch (e) {
throw new Error(
`Failed to find ${aliasKind} definition for "${typeName}", please check that you have a valid codegen typescript file`,
);
}
}
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}.`;
}
extractTypeFromTypeAnnotation(typeAnnotation: $FlowFixMe): string {
return typeAnnotation.type === 'TSTypeReference'
? typeAnnotation.typeName.name
: typeAnnotation.type;
}
getObjectProperties(typeAnnotation: $FlowFixMe): $FlowFixMe {
return typeAnnotation.members;
}
getLiteralValue(option: $FlowFixMe): $FlowFixMe {
return option.literal.value;
}
getPaperTopLevelNameDeprecated(typeAnnotation: $FlowFixMe): $FlowFixMe {
return typeAnnotation.typeParameters.params.length > 1
? typeAnnotation.typeParameters.params[1].literal.value
: null;
}
}
module.exports = {
TypeScriptParser,
};

169
node_modules/@react-native/codegen/lib/parsers/utils.js generated vendored Normal file
View File

@@ -0,0 +1,169 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const {ParserError} = require('./errors');
const path = require('path');
function extractNativeModuleName(filename) {
// this should drop everything after the file name. For Example it will drop:
// .android.js, .android.ts, .android.tsx, .ios.js, .ios.ts, .ios.tsx, .js, .ts, .tsx
return path.basename(filename).split('.')[0];
}
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
function createParserErrorCapturer() {
// $FlowFixMe[missing-empty-array-annot]
const errors = [];
function guard(fn) {
try {
return fn();
} catch (error) {
if (!(error instanceof ParserError)) {
throw error;
}
// $FlowFixMe[incompatible-type]
errors.push(error);
return null;
}
}
// $FlowFixMe[incompatible-type]
return [errors, guard];
}
function verifyPlatforms(hasteModuleName, moduleName) {
let cxxOnly = false;
const excludedPlatforms = new Set();
const namesToValidate = [moduleName, hasteModuleName];
namesToValidate.forEach(name => {
if (name.endsWith('Android')) {
excludedPlatforms.add('iOS');
return;
}
if (name.endsWith('IOS')) {
excludedPlatforms.add('android');
return;
}
if (name.endsWith('Windows')) {
excludedPlatforms.add('iOS');
excludedPlatforms.add('android');
return;
}
if (name.endsWith('Cxx')) {
cxxOnly = true;
excludedPlatforms.add('iOS');
excludedPlatforms.add('android');
return;
}
});
return {
cxxOnly,
excludedPlatforms: Array.from(excludedPlatforms),
};
}
// TODO(T108222691): Use flow-types for @babel/parser
function visit(astNode, visitor) {
const queue = [astNode];
while (queue.length !== 0) {
let item = queue.shift();
if (!(typeof item === 'object' && item != null)) {
continue;
}
if (
typeof item.type === 'string' &&
typeof visitor[item.type] === 'function'
) {
// Don't visit any children
visitor[item.type](item);
} else if (Array.isArray(item)) {
queue.push(...item);
} else {
queue.push(...Object.values(item));
}
}
}
function getConfigType(
// TODO(T71778680): Flow-type this node.
ast,
Visitor,
) {
let infoMap = {
isComponent: false,
isModule: false,
};
visit(ast, Visitor(infoMap));
const {isModule, isComponent} = infoMap;
if (isModule && isComponent) {
throw new Error(
'Found type extending "TurboModule" and exported "codegenNativeComponent" declaration in one file. Split them into separated files.',
);
}
if (isModule) {
return 'module';
} else if (isComponent) {
return 'component';
} else {
return 'none';
}
}
// TODO(T71778680): Flow-type ASTNodes.
function isModuleRegistryCall(node) {
if (node.type !== 'CallExpression') {
return false;
}
const callExpression = node;
if (callExpression.callee.type !== 'MemberExpression') {
return false;
}
const memberExpression = callExpression.callee;
if (
!(
memberExpression.object.type === 'Identifier' &&
memberExpression.object.name === 'TurboModuleRegistry'
)
) {
return false;
}
if (
!(
memberExpression.property.type === 'Identifier' &&
(memberExpression.property.name === 'get' ||
memberExpression.property.name === 'getEnforcing')
)
) {
return false;
}
if (memberExpression.computed) {
return false;
}
return true;
}
function getSortedObject(unsortedObject) {
return Object.keys(unsortedObject)
.sort()
.reduce((sortedObject, key) => {
sortedObject[key] = unsortedObject[key];
return sortedObject;
}, {});
}
module.exports = {
getConfigType,
extractNativeModuleName,
createParserErrorCapturer,
verifyPlatforms,
visit,
isModuleRegistryCall,
getSortedObject,
};

View File

@@ -0,0 +1,225 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
const {ParserError} = require('./errors');
const path = require('path');
export type TypeDeclarationMap = {[declarationName: string]: $FlowFixMe};
export type TypeResolutionStatus =
| $ReadOnly<{
type: 'alias' | 'enum',
successful: true,
name: string,
}>
| $ReadOnly<{
successful: false,
}>;
function extractNativeModuleName(filename: string): string {
// this should drop everything after the file name. For Example it will drop:
// .android.js, .android.ts, .android.tsx, .ios.js, .ios.ts, .ios.tsx, .js, .ts, .tsx
return path.basename(filename).split('.')[0];
}
export type ParserErrorCapturer = <T>(fn: () => T) => ?T;
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
export type PropAST = Object;
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
export type ASTNode = Object;
function createParserErrorCapturer(): [
Array<ParserError>,
ParserErrorCapturer,
] {
// $FlowFixMe[missing-empty-array-annot]
const errors = [];
function guard<T>(fn: () => T): ?T {
try {
return fn();
} catch (error) {
if (!(error instanceof ParserError)) {
throw error;
}
// $FlowFixMe[incompatible-type]
errors.push(error);
return null;
}
}
// $FlowFixMe[incompatible-type]
return [errors, guard];
}
function verifyPlatforms(
hasteModuleName: string,
moduleName: string,
): $ReadOnly<{
cxxOnly: boolean,
excludedPlatforms: Array<'iOS' | 'android'>,
}> {
let cxxOnly = false;
const excludedPlatforms = new Set<'iOS' | 'android'>();
const namesToValidate = [moduleName, hasteModuleName];
namesToValidate.forEach(name => {
if (name.endsWith('Android')) {
excludedPlatforms.add('iOS');
return;
}
if (name.endsWith('IOS')) {
excludedPlatforms.add('android');
return;
}
if (name.endsWith('Windows')) {
excludedPlatforms.add('iOS');
excludedPlatforms.add('android');
return;
}
if (name.endsWith('Cxx')) {
cxxOnly = true;
excludedPlatforms.add('iOS');
excludedPlatforms.add('android');
return;
}
});
return {
cxxOnly,
excludedPlatforms: Array.from(excludedPlatforms),
};
}
// TODO(T108222691): Use flow-types for @babel/parser
function visit(
astNode: $FlowFixMe,
visitor: {
[type: string]: (node: $FlowFixMe) => void,
},
) {
const queue = [astNode];
while (queue.length !== 0) {
let item = queue.shift();
if (!(typeof item === 'object' && item != null)) {
continue;
}
if (
typeof item.type === 'string' &&
typeof visitor[item.type] === 'function'
) {
// Don't visit any children
visitor[item.type](item);
} else if (Array.isArray(item)) {
queue.push(...item);
} else {
queue.push(...Object.values(item));
}
}
}
function getConfigType(
// TODO(T71778680): Flow-type this node.
ast: $FlowFixMe,
Visitor: ({isComponent: boolean, isModule: boolean}) => {
[type: string]: (node: $FlowFixMe) => void,
},
): 'module' | 'component' | 'none' {
let infoMap = {
isComponent: false,
isModule: false,
};
visit(ast, Visitor(infoMap));
const {isModule, isComponent} = infoMap;
if (isModule && isComponent) {
throw new Error(
'Found type extending "TurboModule" and exported "codegenNativeComponent" declaration in one file. Split them into separated files.',
);
}
if (isModule) {
return 'module';
} else if (isComponent) {
return 'component';
} else {
return 'none';
}
}
// TODO(T71778680): Flow-type ASTNodes.
function isModuleRegistryCall(node: $FlowFixMe): boolean {
if (node.type !== 'CallExpression') {
return false;
}
const callExpression = node;
if (callExpression.callee.type !== 'MemberExpression') {
return false;
}
const memberExpression = callExpression.callee;
if (
!(
memberExpression.object.type === 'Identifier' &&
memberExpression.object.name === 'TurboModuleRegistry'
)
) {
return false;
}
if (
!(
memberExpression.property.type === 'Identifier' &&
(memberExpression.property.name === 'get' ||
memberExpression.property.name === 'getEnforcing')
)
) {
return false;
}
if (memberExpression.computed) {
return false;
}
return true;
}
function getSortedObject<T>(unsortedObject: {[key: string]: T}): {
[key: string]: T,
} {
return Object.keys(unsortedObject)
.sort()
.reduce((sortedObject: {[key: string]: T}, key: string) => {
sortedObject[key] = unsortedObject[key];
return sortedObject;
}, {});
}
module.exports = {
getConfigType,
extractNativeModuleName,
createParserErrorCapturer,
verifyPlatforms,
visit,
isModuleRegistryCall,
getSortedObject,
};