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,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,
};