Skip to content

Commit

Permalink
Refactor codegen: Dispatch props and events from a central place. (#3…
Browse files Browse the repository at this point in the history
…4975)

Summary:
Refer to `In component, props and events pick properties from the component interface by themselves independently, I would like to reverse the direction, to have a function dispatching properties to props and events, to reduce duplicated code.
` in #34872

## Changelog

[General] [Changed] - Refactor codegen: Dispatch props and events from a central place.

Pull Request resolved: #34975

Test Plan: `yarn jest react-native-codegen` passed

Reviewed By: cortinico

Differential Revision: D40507917

Pulled By: cipolleschi

fbshipit-source-id: 71988877008ae11a01affd31b34bb3a3b88f96be
  • Loading branch information
ZihanChen-MSFT authored and facebook-github-bot committed Oct 20, 2022
1 parent f5bd272 commit affcfa7
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -426,26 +426,6 @@ function getTypeAnnotation<T>(
}
}

function isProp(name: string, typeAnnotation: $FlowFixMe) {
if (typeAnnotation.type === 'TSTypeReference') {
// Remove unwanted types
if (
typeAnnotation.typeName.name === 'DirectEventHandler' ||
typeAnnotation.typeName.name === 'BubblingEventHandler'
) {
return false;
}
if (
name === 'style' &&
typeAnnotation.type === 'GenericTypeAnnotation' &&
typeAnnotation.typeName.name === 'ViewStyleProp'
) {
return false;
}
}
return true;
}

type SchemaInfo = {
name: string,
optional: boolean,
Expand All @@ -456,17 +436,14 @@ type SchemaInfo = {
function getSchemaInfo(
property: PropAST,
types: TypeDeclarationMap,
): ?SchemaInfo {
): SchemaInfo {
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(
property.typeAnnotation.typeAnnotation,
types,
);

const name = property.key.name;
if (!isProp(name, topLevelType.type)) {
return null;
}

if (!property.optional && topLevelType.defaultValue !== undefined) {
throw new Error(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,42 +197,30 @@ function getEventArgument(argumentProps, name: $FlowFixMe) {
};
}

function isEvent(typeAnnotation: $FlowFixMe) {
switch (typeAnnotation.type) {
case 'TSTypeReference':
if (
typeAnnotation.typeName.name !== 'BubblingEventHandler' &&
typeAnnotation.typeName.name !== 'DirectEventHandler'
) {
return false;
} else {
return true;
}
default:
return false;
}
}
// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser
type EventTypeAST = Object;

function buildEventSchema(
types: TypeDeclarationMap,
property: EventTypeAST,
): ?EventTypeShape {
): EventTypeShape {
// unpack WithDefault, (T) or T|U
const topLevelType = parseTopLevelType(
property.typeAnnotation.typeAnnotation,
types,
);
if (!isEvent(topLevelType.type)) {
return null;
}

const name = property.key.name;
const typeAnnotation = topLevelType.type;
const optional = property.optional || topLevelType.optional;
const {argumentProps, bubblingType, paperTopLevelNameDeprecated} =
findEventArgumentsAndType(typeAnnotation, types);

if (bubblingType && argumentProps) {
if (!argumentProps) {
throw new Error(`Unable to determine event arguments for "${name}"`);
} else if (!bubblingType) {
throw new Error(`Unable to determine event bubbling type for "${name}"`);
} else {
if (paperTopLevelNameDeprecated != null) {
return {
name,
Expand All @@ -256,27 +244,13 @@ function buildEventSchema(
},
};
}

if (argumentProps === null) {
throw new Error(`Unable to determine event arguments for "${name}"`);
}

if (bubblingType === null) {
throw new Error(`Unable to determine event arguments for "${name}"`);
}
}

// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser
type EventTypeAST = Object;

function getEvents(
eventTypeAST: $ReadOnlyArray<EventTypeAST>,
types: TypeDeclarationMap,
): $ReadOnlyArray<EventTypeShape> {
return eventTypeAST
.filter(property => property.type === 'TSPropertySignature')
.map(property => buildEventSchema(types, property))
.filter(Boolean);
return eventTypeAST.map(property => buildEventSchema(types, property));
}

module.exports = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import type {ExtendsPropsShape} from '../../../CodegenSchema.js';
import type {TypeDeclarationMap} from '../../utils';
const {parseTopLevelType} = require('../parseTopLevelType');
const {flattenProperties} = require('./componentsUtils.js');

function extendsForProp(prop: PropsAST, types: TypeDeclarationMap) {
if (!prop.expression) {
Expand All @@ -36,31 +38,66 @@ function extendsForProp(prop: PropsAST, types: TypeDeclarationMap) {
}
}

function removeKnownExtends(
typeDefinition: $ReadOnlyArray<PropsAST>,
types: TypeDeclarationMap,
): $ReadOnlyArray<PropsAST> {
return typeDefinition.filter(
prop =>
prop.type !== 'TSExpressionWithTypeArguments' ||
extendsForProp(prop, types) === null,
);
function isEvent(typeAnnotation: $FlowFixMe): boolean {
if (typeAnnotation.type !== 'TSTypeReference') {
return false;
}
const eventNames = new Set(['BubblingEventHandler', 'DirectEventHandler']);
return eventNames.has(typeAnnotation.typeName.name);
}

function isProp(name: string, typeAnnotation: $FlowFixMe): boolean {
if (typeAnnotation.type !== 'TSTypeReference') {
return true;
}
const isStyle =
name === 'style' &&
typeAnnotation.type === 'GenericTypeAnnotation' &&
typeAnnotation.typeName.name === 'ViewStyleProp';
return !isStyle;
}

// $FlowFixMe[unclear-type] TODO(T108222691): Use flow-types for @babel/parser
type PropsAST = Object;

function getExtendsProps(
function categorizeProps(
typeDefinition: $ReadOnlyArray<PropsAST>,
types: TypeDeclarationMap,
): $ReadOnlyArray<ExtendsPropsShape> {
return typeDefinition
.filter(prop => prop.type === 'TSExpressionWithTypeArguments')
.map(prop => extendsForProp(prop, types))
.filter(Boolean);
extendsProps: Array<ExtendsPropsShape>,
props: Array<PropsAST>,
events: Array<PropsAST>,
): void {
const remaining: Array<PropsAST> = [];
for (const prop of typeDefinition) {
// find extends
if (prop.type === 'TSExpressionWithTypeArguments') {
const extend = extendsForProp(prop, types);
if (extend) {
extendsProps.push(extend);
continue;
}
}

remaining.push(prop);
}

// find events and props
for (const prop of flattenProperties(remaining, types)) {
if (prop.type === 'TSPropertySignature') {
const topLevelType = parseTopLevelType(
prop.typeAnnotation.typeAnnotation,
types,
);

if (isEvent(topLevelType.type)) {
events.push(prop);
} else if (isProp(prop.key.name, prop)) {
props.push(prop);
}
}
}
}

module.exports = {
getExtendsProps,
removeKnownExtends,
categorizeProps,
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
*/

'use strict';
import type {ExtendsPropsShape} from '../../../CodegenSchema.js';
import type {TypeDeclarationMap} from '../../utils';
import type {CommandOptions} from './options';
import type {ComponentSchemaBuilderConfig} from './schema.js';

const {getTypes} = require('../utils');
const {getCommands} = require('./commands');
const {getEvents} = require('./events');
const {getExtendsProps, removeKnownExtends} = require('./extends');
const {categorizeProps} = require('./extends');
const {getCommandOptions, getOptions} = require('./options');
const {getProps} = require('./props');
const {getProperties} = require('./componentsUtils.js');
Expand Down Expand Up @@ -180,6 +181,9 @@ function getCommandProperties(
return properties;
}

// $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
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
Expand All @@ -203,12 +207,20 @@ function buildComponentSchema(ast): ComponentSchemaBuilderConfig {
commandOptions,
);

const extendsProps = getExtendsProps(propProperties, types);
const options = getOptions(optionsExpression);

const nonExtendsProps = removeKnownExtends(propProperties, types);
const props = getProps(nonExtendsProps, types);
const events = getEvents(propProperties, types);
const extendsProps: Array<ExtendsPropsShape> = [];
const componentPropAsts: Array<PropsAST> = [];
const componentEventAsts: Array<PropsAST> = [];
categorizeProps(
propProperties,
types,
extendsProps,
componentPropAsts,
componentEventAsts,
);
const props = getProps(componentPropAsts, types);
const events = getEvents(componentEventAsts, types);
const commands = getCommands(commandProperties, types);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@
*/

'use strict';
const {
flattenProperties,
getSchemaInfo,
getTypeAnnotation,
} = require('./componentsUtils.js');
const {getSchemaInfo, getTypeAnnotation} = require('./componentsUtils.js');

import type {NamedShape, PropTypeAnnotation} from '../../../CodegenSchema.js';
import type {TypeDeclarationMap} from '../../utils';
Expand All @@ -24,11 +20,8 @@ type PropAST = Object;
function buildPropSchema(
property: PropAST,
types: TypeDeclarationMap,
): ?NamedShape<PropTypeAnnotation> {
): NamedShape<PropTypeAnnotation> {
const info = getSchemaInfo(property, types);
if (info == null) {
return null;
}
const {name, optional, typeAnnotation, defaultValue} = info;
return {
name,
Expand All @@ -47,9 +40,7 @@ function getProps(
typeDefinition: $ReadOnlyArray<PropAST>,
types: TypeDeclarationMap,
): $ReadOnlyArray<NamedShape<PropTypeAnnotation>> {
return flattenProperties(typeDefinition, types)
.map(property => buildPropSchema(property, types))
.filter(Boolean);
return typeDefinition.map(property => buildPropSchema(property, types));
}

module.exports = {
Expand Down

0 comments on commit affcfa7

Please sign in to comment.