mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Parse custom NativeState in Flow (#34753)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/34753 This Diff introduce a the capability to parse custom NativeStates in Flow. To achieve this I also had to define the CodegenSchema. The parsing follows the exact same rules as props, as initial heuristic. This should allow enough customization for the developers who needs a custom state. There is only a case I was not able to make it work that is STATE_ALIASED_LOCALLY, from the fixtures. I don't know how diffuse it is and I think we can live with some workarounds for the time being. This diff also adds tests for the custom Native State Flow Parser. ## Changelog [General][Added] - Implement custom Native State parsing in Flow Reviewed By: cortinico Differential Revision: D39686251 fbshipit-source-id: 446997a39b33b7e9351d5ba12cecaeff33df4d16
This commit is contained in:
committed by
Facebook GitHub Bot
parent
5e79fa8441
commit
925b15351f
@@ -80,6 +80,7 @@ export type ComponentShape = $ReadOnly<{
|
||||
events: $ReadOnlyArray<EventTypeShape>,
|
||||
props: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
|
||||
commands: $ReadOnlyArray<NamedShape<CommandTypeAnnotation>>,
|
||||
state?: $ReadOnlyArray<NamedShape<StateTypeAnnotation>>,
|
||||
}>;
|
||||
|
||||
export type OptionsShape = $ReadOnly<{
|
||||
@@ -185,6 +186,8 @@ export type ReservedPropTypeAnnotation = $ReadOnly<{
|
||||
| 'EdgeInsetsPrimitive',
|
||||
}>;
|
||||
|
||||
export type StateTypeAnnotation = PropTypeAnnotation;
|
||||
|
||||
export type CommandTypeAnnotation = FunctionTypeAnnotation<
|
||||
CommandParamTypeAnnotation,
|
||||
VoidTypeAnnotation,
|
||||
|
||||
Vendored
+11
-2
@@ -14,10 +14,19 @@ const {compareSnaps, compareTsArraySnaps} = require('../compareSnaps.js');
|
||||
|
||||
const flowFixtures = require('../../flow/components/__test_fixtures__/fixtures.js');
|
||||
const flowSnaps = require('../../../../src/parsers/flow/components/__tests__/__snapshots__/component-parser-test.js.snap');
|
||||
const flowExtraCases = [];
|
||||
const flowExtraCases = [
|
||||
//TODO: remove these once we implement TypeScript parser for Custom State
|
||||
'ALL_STATE_TYPES',
|
||||
'ARRAY_STATE_TYPES',
|
||||
'COMMANDS_EVENTS_STATE_TYPES_EXPORTED',
|
||||
'OBJECT_STATE_TYPES',
|
||||
];
|
||||
const tsFixtures = require('../../typescript/components/__test_fixtures__/fixtures.js');
|
||||
const tsSnaps = require('../../../../src/parsers/typescript/components/__tests__/__snapshots__/typescript-component-parser-test.js.snap');
|
||||
const tsExtraCases = ['ARRAY2_PROP_TYPES_NO_EVENTS'];
|
||||
const tsExtraCases = ['ARRAY2_PROP_TYPES_NO_EVENTS'].concat([
|
||||
//TODO: remove these once we implement TypeScript parser for Custom State
|
||||
'COMMANDS_AND_EVENTS_TYPES_EXPORTED',
|
||||
]);
|
||||
const ignoredCases = ['ARRAY_PROP_TYPES_NO_EVENTS'];
|
||||
|
||||
compareSnaps(
|
||||
|
||||
+408
@@ -579,6 +579,402 @@ export default (codegenNativeComponent<ModuleProps>(
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
// === STATE ===
|
||||
const NULLABLE_STATE_WITH_DEFAULT = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
import type {WithDefault, Float} from 'CodegenTypes';
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|
||||
|}>;
|
||||
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
nullable_with_default: ?WithDefault<Float, 1.0>,
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const NON_OPTIONAL_KEY_STATE_WITH_DEFAULT_VALUE = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
import type {WithDefault, Float} from 'CodegenTypes';
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
required_key_with_default: WithDefault<Float, 1.0>,
|
||||
|}>;
|
||||
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
required_key_with_default: WithDefault<Float, 1.0>,
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const STATE_CONFLICT_NAMES = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
isEnabled: string,
|
||||
|
||||
isEnabled: boolean,
|
||||
|}>;
|
||||
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
isEnabled: string,
|
||||
|
||||
isEnabled: boolean,
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const STATE_CONFLICT_WITH_SPREAD_PROPS = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
type StateInFile = $ReadOnly<{|
|
||||
isEnabled: boolean,
|
||||
|}>;
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
...StateInFile,
|
||||
isEnabled: boolean,
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const STATE_SPREAD_CONFLICTS_WITH_PROPS = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
type StateInFile = $ReadOnly<{|
|
||||
isEnabled: boolean,
|
||||
|}>;
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
isEnabled: boolean,
|
||||
...StateInFile,
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const STATE_NUMBER_TYPE = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
someProp: number
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const STATE_MIXED_ENUM = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
someProp?: WithDefault<'foo' | 1, 1>
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const STATE_ENUM_BOOLEAN = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
someProp?: WithDefault<false | true, false>
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const STATE_ARRAY_MIXED_ENUM = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
someProp?: WithDefault<$ReadOnlyArray<'foo' | 1>, 1>
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const STATE_ARRAY_ENUM_BOOLEAN = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
someProp?: WithDefault<$ReadOnlyArray<false | true>, false>
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const STATE_ARRAY_ENUM_INT = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
someProp?: WithDefault<$ReadOnlyArray<0 | 1>, 0>
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const DOUBLE_STATE_IN_FILE = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
export type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
type SecondNativeState = $ReadOnly<{|
|
||||
someProp: boolean
|
||||
|}>;
|
||||
|
||||
export type FirstNativeState = $ReadOnly<{|
|
||||
someOtherProp: boolean
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
module.exports = {
|
||||
COMMANDS_DEFINED_INLINE,
|
||||
COMMANDS_DEFINED_MULTIPLE_TIMES,
|
||||
@@ -597,4 +993,16 @@ module.exports = {
|
||||
PROP_ARRAY_MIXED_ENUM,
|
||||
PROP_ARRAY_ENUM_BOOLEAN,
|
||||
PROP_ARRAY_ENUM_INT,
|
||||
NULLABLE_STATE_WITH_DEFAULT,
|
||||
NON_OPTIONAL_KEY_STATE_WITH_DEFAULT_VALUE,
|
||||
STATE_CONFLICT_NAMES,
|
||||
STATE_CONFLICT_WITH_SPREAD_PROPS,
|
||||
STATE_SPREAD_CONFLICTS_WITH_PROPS,
|
||||
STATE_NUMBER_TYPE,
|
||||
STATE_MIXED_ENUM,
|
||||
STATE_ENUM_BOOLEAN,
|
||||
STATE_ARRAY_MIXED_ENUM,
|
||||
STATE_ARRAY_ENUM_BOOLEAN,
|
||||
STATE_ARRAY_ENUM_INT,
|
||||
DOUBLE_STATE_IN_FILE,
|
||||
};
|
||||
|
||||
+436
-2
@@ -939,7 +939,7 @@ export default (codegenNativeComponent<ModuleProps>(
|
||||
): NativeType);
|
||||
`;
|
||||
|
||||
const COMMANDS_AND_EVENTS_TYPES_EXPORTED = `
|
||||
const COMMANDS_EVENTS_STATE_TYPES_EXPORTED = `
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
@@ -981,6 +981,13 @@ export type ModuleProps = $ReadOnly<{|
|
||||
onDirectEventDefinedInlineWithPaperName: DirectEventHandler<EventInFile, 'paperDirectEventDefinedInlineWithPaperName'>,
|
||||
|}>;
|
||||
|
||||
// Add state here
|
||||
export type ModuleNativeState = $ReadOnly<{|
|
||||
boolean_required: boolean,
|
||||
boolean_optional_key?: WithDefault<boolean, true>,
|
||||
boolean_optional_both?: WithDefault<boolean, true>,
|
||||
|}>
|
||||
|
||||
type NativeType = HostComponent<ModuleProps>;
|
||||
|
||||
export type ScrollTo = (viewRef: React.ElementRef<NativeType>, y: Int, animated: Boolean) => Void;
|
||||
@@ -998,6 +1005,429 @@ export default (codegenNativeComponent<ModuleProps>(
|
||||
): NativeType);
|
||||
`;
|
||||
|
||||
// === STATE === //
|
||||
const ALL_STATE_TYPES = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
import type {Int32, Double, Float, WithDefault} from 'CodegenTypes';
|
||||
import type {ImageSource} from 'ImageSource';
|
||||
import type {ColorValue, ColorArrayValue, PointValue, EdgeInsetsValue} from 'StyleSheetTypes';
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
type ModuleNativeState = $ReadOnly<{|
|
||||
// Boolean props
|
||||
boolean_required: boolean,
|
||||
boolean_optional_key?: WithDefault<boolean, true>,
|
||||
boolean_optional_both?: WithDefault<boolean, true>,
|
||||
|
||||
// Boolean props, null default
|
||||
boolean_null_optional_key?: WithDefault<boolean, null>,
|
||||
boolean_null_optional_both?: WithDefault<boolean, null>,
|
||||
|
||||
// String props
|
||||
string_required: string,
|
||||
string_optional_key?: WithDefault<string, ''>,
|
||||
string_optional_both?: WithDefault<string, ''>,
|
||||
|
||||
// String props, null default
|
||||
string_null_optional_key?: WithDefault<string, null>,
|
||||
string_null_optional_both?: WithDefault<string, null>,
|
||||
|
||||
// Stringish props
|
||||
stringish_required: Stringish,
|
||||
stringish_optional_key?: WithDefault<Stringish, ''>,
|
||||
stringish_optional_both?: WithDefault<Stringish, ''>,
|
||||
|
||||
// Stringish props, null default
|
||||
stringish_null_optional_key?: WithDefault<Stringish, null>,
|
||||
stringish_null_optional_both?: WithDefault<Stringish, null>,
|
||||
|
||||
// Double props
|
||||
double_required: Double,
|
||||
double_optional_key?: WithDefault<Double, 1.1>,
|
||||
double_optional_both?: WithDefault<Double, 1.1>,
|
||||
|
||||
// Float props
|
||||
float_required: Float,
|
||||
float_optional_key?: WithDefault<Float, 1.1>,
|
||||
float_optional_both?: WithDefault<Float, 1.1>,
|
||||
|
||||
// Float props, null default
|
||||
float_null_optional_key?: WithDefault<Float, null>,
|
||||
float_null_optional_both?: WithDefault<Float, null>,
|
||||
|
||||
// Int32 props
|
||||
int32_required: Int32,
|
||||
int32_optional_key?: WithDefault<Int32, 1>,
|
||||
int32_optional_both?: WithDefault<Int32, 1>,
|
||||
|
||||
// String enum props
|
||||
enum_optional_key?: WithDefault<'small' | 'large', 'small'>,
|
||||
enum_optional_both?: WithDefault<'small' | 'large', 'small'>,
|
||||
|
||||
// Int enum props
|
||||
int_enum_optional_key?: WithDefault<0 | 1, 0>,
|
||||
|
||||
// Object props
|
||||
object_optional_key?: $ReadOnly<{| prop: string |}>,
|
||||
object_optional_both?: ?$ReadOnly<{| prop: string |}>,
|
||||
object_optional_value: ?$ReadOnly<{| prop: string |}>,
|
||||
|
||||
// ImageSource props
|
||||
image_required: ImageSource,
|
||||
image_optional_value: ?ImageSource,
|
||||
image_optional_both?: ?ImageSource,
|
||||
|
||||
// ColorValue props
|
||||
color_required: ColorValue,
|
||||
color_optional_key?: ColorValue,
|
||||
color_optional_value: ?ColorValue,
|
||||
color_optional_both?: ?ColorValue,
|
||||
|
||||
// ColorArrayValue props
|
||||
color_array_required: ColorArrayValue,
|
||||
color_array_optional_key?: ColorArrayValue,
|
||||
color_array_optional_value: ?ColorArrayValue,
|
||||
color_array_optional_both?: ?ColorArrayValue,
|
||||
|
||||
// ProcessedColorValue props
|
||||
processed_color_required: ProcessedColorValue,
|
||||
processed_color_optional_key?: ProcessedColorValue,
|
||||
processed_color_optional_value: ?ProcessedColorValue,
|
||||
processed_color_optional_both?: ?ProcessedColorValue,
|
||||
|
||||
// PointValue props
|
||||
point_required: PointValue,
|
||||
point_optional_key?: PointValue,
|
||||
point_optional_value: ?PointValue,
|
||||
point_optional_both?: ?PointValue,
|
||||
|
||||
// EdgeInsets props
|
||||
insets_required: EdgeInsetsValue,
|
||||
insets_optional_key?: EdgeInsetsValue,
|
||||
insets_optional_value: ?EdgeInsetsValue,
|
||||
insets_optional_both?: ?EdgeInsetsValue,
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps, Options>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const ARRAY_STATE_TYPES = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
import type {Int32, Double, Float, WithDefault} from 'CodegenTypes';
|
||||
import type {ImageSource} from 'ImageSource';
|
||||
import type {ColorValue, PointValue, ProcessColorValue, EdgeInsetsValue} from 'StyleSheetTypes';
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
type ObjectType = $ReadOnly<{| prop: string |}>;
|
||||
type ArrayObjectType = $ReadOnlyArray<$ReadOnly<{| prop: string |}>>;
|
||||
|
||||
type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
type ModuleNativeState = $ReadOnly<{|
|
||||
// Props
|
||||
// Boolean props
|
||||
array_boolean_required: $ReadOnlyArray<boolean>,
|
||||
array_boolean_optional_key?: $ReadOnlyArray<boolean>,
|
||||
array_boolean_optional_value: ?$ReadOnlyArray<boolean>,
|
||||
array_boolean_optional_both?: ?$ReadOnlyArray<boolean>,
|
||||
|
||||
// String props
|
||||
array_string_required: $ReadOnlyArray<string>,
|
||||
array_string_optional_key?: $ReadOnlyArray<string>,
|
||||
array_string_optional_value: ?$ReadOnlyArray<string>,
|
||||
array_string_optional_both?: ?$ReadOnlyArray<string>,
|
||||
|
||||
// Double props
|
||||
array_double_required: $ReadOnlyArray<Double>,
|
||||
array_double_optional_key?: $ReadOnlyArray<Double>,
|
||||
array_double_optional_value: ?$ReadOnlyArray<Double>,
|
||||
array_double_optional_both?: ?$ReadOnlyArray<Double>,
|
||||
|
||||
// Float props
|
||||
array_float_required: $ReadOnlyArray<Float>,
|
||||
array_float_optional_key?: $ReadOnlyArray<Float>,
|
||||
array_float_optional_value: ?$ReadOnlyArray<Float>,
|
||||
array_float_optional_both?: ?$ReadOnlyArray<Float>,
|
||||
|
||||
// Int32 props
|
||||
array_int32_required: $ReadOnlyArray<Int32>,
|
||||
array_int32_optional_key?: $ReadOnlyArray<Int32>,
|
||||
array_int32_optional_value: ?$ReadOnlyArray<Int32>,
|
||||
array_int32_optional_both?: ?$ReadOnlyArray<Int32>,
|
||||
|
||||
// String enum props
|
||||
array_enum_optional_key?: WithDefault<
|
||||
$ReadOnlyArray<'small' | 'large'>,
|
||||
'small',
|
||||
>,
|
||||
array_enum_optional_both?: WithDefault<
|
||||
$ReadOnlyArray<'small' | 'large'>,
|
||||
'small',
|
||||
>,
|
||||
|
||||
// ImageSource props
|
||||
array_image_required: $ReadOnlyArray<ImageSource>,
|
||||
array_image_optional_key?: $ReadOnlyArray<ImageSource>,
|
||||
array_image_optional_value: ?$ReadOnlyArray<ImageSource>,
|
||||
array_image_optional_both?: ?$ReadOnlyArray<ImageSource>,
|
||||
|
||||
// ColorValue props
|
||||
array_color_required: $ReadOnlyArray<ColorValue>,
|
||||
array_color_optional_key?: $ReadOnlyArray<ColorValue>,
|
||||
array_color_optional_value: ?$ReadOnlyArray<ColorValue>,
|
||||
array_color_optional_both?: ?$ReadOnlyArray<ColorValue>,
|
||||
|
||||
// PointValue props
|
||||
array_point_required: $ReadOnlyArray<PointValue>,
|
||||
array_point_optional_key?: $ReadOnlyArray<PointValue>,
|
||||
array_point_optional_value: ?$ReadOnlyArray<PointValue>,
|
||||
array_point_optional_both?: ?$ReadOnlyArray<PointValue>,
|
||||
|
||||
// EdgeInsetsValue props
|
||||
array_insets_required: $ReadOnlyArray<EdgeInsetsValue>,
|
||||
array_insets_optional_key?: $ReadOnlyArray<EdgeInsetsValue>,
|
||||
array_insets_optional_value: ?$ReadOnlyArray<EdgeInsetsValue>,
|
||||
array_insets_optional_both?: ?$ReadOnlyArray<EdgeInsetsValue>,
|
||||
|
||||
// Object props
|
||||
array_object_required: $ReadOnlyArray<$ReadOnly<{| prop: string |}>>,
|
||||
array_object_optional_key?: $ReadOnlyArray<$ReadOnly<{| prop: string |}>>,
|
||||
array_object_optional_value: ?ArrayObjectType,
|
||||
array_object_optional_both?: ?$ReadOnlyArray<ObjectType>,
|
||||
|
||||
// Nested array object types
|
||||
array_of_array_object_required: $ReadOnlyArray<
|
||||
$ReadOnly<{|
|
||||
// This needs to be the same name as the top level array above
|
||||
array_object_required: $ReadOnlyArray<$ReadOnly<{| prop: string |}>>,
|
||||
|}>
|
||||
>,
|
||||
array_of_array_object_optional_key?: $ReadOnlyArray<
|
||||
$ReadOnly<{|
|
||||
// This needs to be the same name as the top level array above
|
||||
array_object_optional_key: $ReadOnlyArray<$ReadOnly<{| prop?: string |}>>,
|
||||
|}>
|
||||
>,
|
||||
array_of_array_object_optional_value: ?$ReadOnlyArray<
|
||||
$ReadOnly<{|
|
||||
// This needs to be the same name as the top level array above
|
||||
array_object_optional_value: $ReadOnlyArray<$ReadOnly<{| prop: ?string |}>>,
|
||||
|}>
|
||||
>,
|
||||
array_of_array_object_optional_both?: ?$ReadOnlyArray<
|
||||
$ReadOnly<{|
|
||||
// This needs to be the same name as the top level array above
|
||||
array_object_optional_both: $ReadOnlyArray<$ReadOnly<{| prop?: ?string |}>>,
|
||||
|}>
|
||||
>,
|
||||
|
||||
// Nested array of array of object types
|
||||
array_of_array_of_object_required: $ReadOnlyArray<
|
||||
$ReadOnlyArray<
|
||||
$ReadOnly<{|
|
||||
prop: string,
|
||||
|}>,
|
||||
>,
|
||||
>,
|
||||
|
||||
// Nested array of array of object types (in file)
|
||||
array_of_array_of_object_required_in_file: $ReadOnlyArray<
|
||||
$ReadOnlyArray<ObjectType>,
|
||||
>,
|
||||
|
||||
// Nested array of array of object types (with spread)
|
||||
array_of_array_of_object_required_with_spread: $ReadOnlyArray<
|
||||
$ReadOnlyArray<
|
||||
$ReadOnly<{|
|
||||
...ObjectType
|
||||
|}>,
|
||||
>,
|
||||
>,
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
const OBJECT_STATE_TYPES = `
|
||||
/**
|
||||
* 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
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
import type {Int32, Double, Float, WithDefault} from 'CodegenTypes';
|
||||
import type {ImageSource} from 'ImageSource';
|
||||
import type {ColorValue, PointValue, EdgeInsetsValue} from 'StyleSheetTypes';
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
import type {HostComponent} from 'react-native';
|
||||
|
||||
type ModuleProps = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|}>;
|
||||
|
||||
type ModuleNativeState = $ReadOnly<{|
|
||||
// Props
|
||||
// Boolean props
|
||||
boolean_required: $ReadOnly<{|prop: boolean|}>,
|
||||
boolean_optional: $ReadOnly<{|prop?: WithDefault<boolean, false>|}>,
|
||||
|
||||
// String props
|
||||
string_required: $ReadOnly<{|prop: string|}>,
|
||||
string_optional: $ReadOnly<{|prop?: WithDefault<string, ''>|}>,
|
||||
|
||||
// Double props
|
||||
double_required: $ReadOnly<{|prop: Double|}>,
|
||||
double_optional: $ReadOnly<{|prop?: WithDefault<Double, 0.0>|}>,
|
||||
|
||||
// Float props
|
||||
float_required: $ReadOnly<{|prop: Float|}>,
|
||||
float_optional: $ReadOnly<{|prop?: WithDefault<Float, 0.0>|}>,
|
||||
|
||||
// Int32 props
|
||||
int_required: $ReadOnly<{|prop: Int32|}>,
|
||||
int_optional: $ReadOnly<{|prop?: WithDefault<Int32, 0>|}>,
|
||||
|
||||
// String enum props
|
||||
enum_optional: $ReadOnly<{|
|
||||
prop?: WithDefault<$ReadOnlyArray<'small' | 'large'>, 'small'>,
|
||||
|}>,
|
||||
|
||||
// ImageSource props
|
||||
image_required: $ReadOnly<{|prop: ImageSource|}>,
|
||||
image_optional_key: $ReadOnly<{|prop?: ImageSource|}>,
|
||||
image_optional_value: $ReadOnly<{|prop: ?ImageSource|}>,
|
||||
image_optional_both: $ReadOnly<{|prop?: ?ImageSource|}>,
|
||||
|
||||
// ColorValue props
|
||||
color_required: $ReadOnly<{|prop: ColorValue|}>,
|
||||
color_optional_key: $ReadOnly<{|prop?: ColorValue|}>,
|
||||
color_optional_value: $ReadOnly<{|prop: ?ColorValue|}>,
|
||||
color_optional_both: $ReadOnly<{|prop?: ?ColorValue|}>,
|
||||
|
||||
// ProcessedColorValue props
|
||||
processed_color_required: $ReadOnly<{|prop: ProcessedColorValue|}>,
|
||||
processed_color_optional_key: $ReadOnly<{|prop?: ProcessedColorValue|}>,
|
||||
processed_color_optional_value: $ReadOnly<{|prop: ?ProcessedColorValue|}>,
|
||||
processed_color_optional_both: $ReadOnly<{|prop?: ?ProcessedColorValue|}>,
|
||||
|
||||
// PointValue props
|
||||
point_required: $ReadOnly<{|prop: PointValue|}>,
|
||||
point_optional_key: $ReadOnly<{|prop?: PointValue|}>,
|
||||
point_optional_value: $ReadOnly<{|prop: ?PointValue|}>,
|
||||
point_optional_both: $ReadOnly<{|prop?: ?PointValue|}>,
|
||||
|
||||
// EdgeInsetsValue props
|
||||
insets_required: $ReadOnly<{|prop: EdgeInsetsValue|}>,
|
||||
insets_optional_key: $ReadOnly<{|prop?: EdgeInsetsValue|}>,
|
||||
insets_optional_value: $ReadOnly<{|prop: ?EdgeInsetsValue|}>,
|
||||
insets_optional_both: $ReadOnly<{|prop?: ?EdgeInsetsValue|}>,
|
||||
|
||||
// Nested object props
|
||||
object_required: $ReadOnly<{|prop: $ReadOnly<{nestedProp: string}>|}>,
|
||||
object_optional_key?: $ReadOnly<{|prop: $ReadOnly<{nestedProp: string}>|}>,
|
||||
object_optional_value: ?$ReadOnly<{|prop: $ReadOnly<{nestedProp: string}>|}>,
|
||||
object_optional_both?: ?$ReadOnly<{|prop: $ReadOnly<{nestedProp: string}>|}>,
|
||||
|}>;
|
||||
|
||||
export default (codegenNativeComponent<ModuleProps>(
|
||||
'Module',
|
||||
): HostComponent<ModuleProps>);
|
||||
`;
|
||||
|
||||
//TODO: fix this. The code is the same as per the props, but it fails with the State.
|
||||
// const STATE_ALIASED_LOCALLY = `
|
||||
// /**
|
||||
// * 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
|
||||
// * @flow strict-local
|
||||
// */
|
||||
|
||||
// 'use strict';
|
||||
|
||||
// import type {ViewProps} from 'ViewPropTypes';
|
||||
// import type {HostComponent} from 'react-native';
|
||||
|
||||
// const codegenNativeComponent = require('codegenNativeComponent');
|
||||
|
||||
// type DeepSpread = $ReadOnly<{|
|
||||
// otherStringProp: string,
|
||||
// |}>;
|
||||
|
||||
// export type StateInFile = $ReadOnly<{|
|
||||
// ...DeepSpread,
|
||||
// isEnabled: boolean,
|
||||
// label: string,
|
||||
// |}>;
|
||||
|
||||
// export type ModuleProps = $ReadOnly<{|
|
||||
// ...ViewProps,
|
||||
// |}>;
|
||||
|
||||
// export type ModuleNativeState = $ReadOnly<{|
|
||||
// ...StateInFile,
|
||||
|
||||
// localType: $ReadOnly<{|
|
||||
// ...StateInFile
|
||||
// |}>,
|
||||
|
||||
// localArr: $ReadOnlyArray<StateInFile>
|
||||
// ||}>;
|
||||
|
||||
// export default (codegenNativeComponent<ModuleProps>(
|
||||
// 'Module',
|
||||
// ): HostComponent<ModuleProps>);
|
||||
// `;
|
||||
|
||||
module.exports = {
|
||||
ALL_PROP_TYPES_NO_EVENTS,
|
||||
ARRAY_PROP_TYPES_NO_EVENTS,
|
||||
@@ -1009,8 +1439,12 @@ module.exports = {
|
||||
EVENTS_DEFINED_INLINE_WITH_ALL_TYPES,
|
||||
EVENTS_DEFINED_AS_NULL_INLINE,
|
||||
PROPS_AND_EVENTS_TYPES_EXPORTED,
|
||||
COMMANDS_AND_EVENTS_TYPES_EXPORTED,
|
||||
COMMANDS_EVENTS_STATE_TYPES_EXPORTED,
|
||||
COMMANDS_DEFINED_WITH_ALL_TYPES,
|
||||
PROPS_AS_EXTERNAL_TYPES,
|
||||
COMMANDS_WITH_EXTERNAL_TYPES,
|
||||
ALL_STATE_TYPES,
|
||||
ARRAY_STATE_TYPES,
|
||||
OBJECT_STATE_TYPES,
|
||||
// STATE_ALIASED_LOCALLY,
|
||||
};
|
||||
|
||||
+2028
-91
File diff suppressed because it is too large
Load Diff
+486
@@ -0,0 +1,486 @@
|
||||
/**
|
||||
* 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 {ASTNode} from '../utils';
|
||||
import type {TypeDeclarationMap} from '../utils.js';
|
||||
import type {NamedShape} from '../../../CodegenSchema.js';
|
||||
const {getValueFromTypes} = require('../utils.js');
|
||||
|
||||
function 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`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeAnnotationForArray<+T>(
|
||||
name: string,
|
||||
typeAnnotation: $FlowFixMe,
|
||||
defaultValue: $FlowFixMe | null,
|
||||
types: TypeDeclarationMap,
|
||||
buildSchema: (property: PropAST, types: TypeDeclarationMap) => ?NamedShape<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' &&
|
||||
extractedTypeAnnotation.id.name === '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,
|
||||
)
|
||||
.map(prop => buildSchema(prop, types))
|
||||
.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,
|
||||
)
|
||||
.map(prop => buildSchema(prop, types))
|
||||
.filter(Boolean),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const type =
|
||||
extractedTypeAnnotation.type === 'GenericTypeAnnotation'
|
||||
? extractedTypeAnnotation.id.name
|
||||
: extractedTypeAnnotation.type;
|
||||
|
||||
switch (type) {
|
||||
case 'ImageSource':
|
||||
return {
|
||||
type: 'ReservedPropTypeAnnotation',
|
||||
name: 'ImageSourcePrimitive',
|
||||
};
|
||||
case 'ColorValue':
|
||||
case 'ProcessedColorValue':
|
||||
return {
|
||||
type: 'ReservedPropTypeAnnotation',
|
||||
name: 'ColorPrimitive',
|
||||
};
|
||||
case 'PointValue':
|
||||
return {
|
||||
type: 'ReservedPropTypeAnnotation',
|
||||
name: 'PointPrimitive',
|
||||
};
|
||||
case 'EdgeInsetsValue':
|
||||
return {
|
||||
type: 'ReservedPropTypeAnnotation',
|
||||
name: 'EdgeInsetsPrimitive',
|
||||
};
|
||||
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 '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:
|
||||
(type: empty);
|
||||
throw new Error(`Unknown property type for "${name}": ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function flattenProperties(
|
||||
typeDefinition: $ReadOnlyArray<PropAST>,
|
||||
types: TypeDeclarationMap,
|
||||
): $ReadOnlyArray<PropAST> {
|
||||
return typeDefinition
|
||||
.map(property => {
|
||||
if (property.type === 'ObjectTypeProperty') {
|
||||
return property;
|
||||
} else if (property.type === 'ObjectTypeSpreadProperty') {
|
||||
return flattenProperties(
|
||||
getProperties(property.argument.id.name, types),
|
||||
types,
|
||||
);
|
||||
}
|
||||
})
|
||||
.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);
|
||||
}
|
||||
|
||||
function verifyPropNotAlreadyDefined(
|
||||
props: $ReadOnlyArray<PropAST>,
|
||||
needleProp: PropAST,
|
||||
) {
|
||||
const propName = needleProp.key.name;
|
||||
const foundProp = props.some(prop => prop.key.name === propName);
|
||||
if (foundProp) {
|
||||
throw new Error(`A prop was already defined with the name ${propName}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeAnnotation<+T>(
|
||||
name: string,
|
||||
annotation: $FlowFixMe | ASTNode,
|
||||
defaultValue: $FlowFixMe | null,
|
||||
withNullDefault: boolean,
|
||||
types: TypeDeclarationMap,
|
||||
buildSchema: (property: PropAST, types: TypeDeclarationMap) => ?NamedShape<T>,
|
||||
): $FlowFixMe {
|
||||
const typeAnnotation = getValueFromTypes(annotation, types);
|
||||
|
||||
if (
|
||||
typeAnnotation.type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === '$ReadOnlyArray'
|
||||
) {
|
||||
return {
|
||||
type: 'ArrayTypeAnnotation',
|
||||
elementType: getTypeAnnotationForArray(
|
||||
name,
|
||||
typeAnnotation.typeParameters.params[0],
|
||||
defaultValue,
|
||||
types,
|
||||
buildSchema,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
typeAnnotation.type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === '$ReadOnly'
|
||||
) {
|
||||
return {
|
||||
type: 'ObjectTypeAnnotation',
|
||||
properties: flattenProperties(
|
||||
typeAnnotation.typeParameters.params[0].properties,
|
||||
types,
|
||||
)
|
||||
.map(prop => buildSchema(prop, types))
|
||||
.filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
const type =
|
||||
typeAnnotation.type === 'GenericTypeAnnotation'
|
||||
? typeAnnotation.id.name
|
||||
: typeAnnotation.type;
|
||||
|
||||
switch (type) {
|
||||
case 'ImageSource':
|
||||
return {
|
||||
type: 'ReservedPropTypeAnnotation',
|
||||
name: 'ImageSourcePrimitive',
|
||||
};
|
||||
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 '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`,
|
||||
);
|
||||
default:
|
||||
(type: empty);
|
||||
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,
|
||||
): ?SchemaInfo {
|
||||
const name = property.key.name;
|
||||
|
||||
const value = getValueFromTypes(property.value, types);
|
||||
let typeAnnotation =
|
||||
value.type === 'NullableTypeAnnotation' ? value.typeAnnotation : value;
|
||||
|
||||
const optional =
|
||||
value.type === 'NullableTypeAnnotation' ||
|
||||
property.optional ||
|
||||
(value.type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === 'WithDefault');
|
||||
|
||||
if (
|
||||
!property.optional &&
|
||||
value.type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === 'WithDefault'
|
||||
) {
|
||||
throw new Error(
|
||||
`key ${name} must be optional if used with WithDefault<> annotation`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
value.type === 'NullableTypeAnnotation' &&
|
||||
typeAnnotation.type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === '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' &&
|
||||
(typeAnnotation.id.name === 'DirectEventHandler' ||
|
||||
typeAnnotation.id.name === 'BubblingEventHandler')
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
name === 'style' &&
|
||||
type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === 'ViewStyleProp'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let defaultValue = null;
|
||||
let withNullDefault = false;
|
||||
if (
|
||||
type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === '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'
|
||||
? typeAnnotation.id.name
|
||||
: typeAnnotation.type;
|
||||
|
||||
if (defaultValueType === 'NullLiteralTypeAnnotation') {
|
||||
defaultValue = null;
|
||||
withNullDefault = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
optional,
|
||||
typeAnnotation,
|
||||
defaultValue,
|
||||
withNullDefault,
|
||||
};
|
||||
}
|
||||
|
||||
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
|
||||
type PropAST = Object;
|
||||
|
||||
module.exports = {
|
||||
getProperties,
|
||||
getSchemaInfo,
|
||||
getTypeAnnotation,
|
||||
flattenProperties,
|
||||
};
|
||||
@@ -16,9 +16,11 @@ import type {ComponentSchemaBuilderConfig} from './schema.js';
|
||||
const {getTypes} = require('../utils');
|
||||
const {getCommands} = require('./commands');
|
||||
const {getEvents} = require('./events');
|
||||
const {getState} = require('./states');
|
||||
const {getExtendsProps, removeKnownExtends} = require('./extends');
|
||||
const {getCommandOptions, getOptions} = require('./options');
|
||||
const {getPropProperties, getProps} = require('./props');
|
||||
const {getProps} = require('./props');
|
||||
const {getProperties} = require('./componentsUtils.js');
|
||||
|
||||
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
|
||||
* LTI update could not be added via codemod */
|
||||
@@ -111,8 +113,32 @@ function findComponentConfig(ast) {
|
||||
throw new Error('codegenNativeCommands may only be called once in a file');
|
||||
}
|
||||
|
||||
const unexportedStateTypes: Array<string> = ast.body
|
||||
.filter(
|
||||
node =>
|
||||
node.type === 'TypeAlias' && node.id.name.indexOf('NativeState') >= 0,
|
||||
)
|
||||
.map(node => node.id.name);
|
||||
|
||||
const exportedStateTypes: Array<string> = namedExports
|
||||
.filter(
|
||||
node =>
|
||||
node.declaration.id &&
|
||||
node.declaration.id.name.indexOf('NativeState') >= 0,
|
||||
)
|
||||
.map(node => node.declaration.id.name);
|
||||
|
||||
const stateTypeName = exportedStateTypes.concat(unexportedStateTypes);
|
||||
|
||||
if (Array.isArray(stateTypeName) && stateTypeName.length > 1) {
|
||||
throw new Error(
|
||||
`Found ${stateTypeName.length} NativeStates for ${foundConfig.componentName}. Each component can have only 1 NativeState`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...foundConfig,
|
||||
stateTypeName: stateTypeName.length === 1 ? stateTypeName[0] : '',
|
||||
commandTypeName:
|
||||
commandsTypeNames[0] == null
|
||||
? null
|
||||
@@ -185,6 +211,7 @@ function buildComponentSchema(ast): ComponentSchemaBuilderConfig {
|
||||
const {
|
||||
componentName,
|
||||
propsTypeName,
|
||||
stateTypeName,
|
||||
commandTypeName,
|
||||
commandOptionsExpression,
|
||||
optionsExpression,
|
||||
@@ -192,7 +219,7 @@ function buildComponentSchema(ast): ComponentSchemaBuilderConfig {
|
||||
|
||||
const types = getTypes(ast);
|
||||
|
||||
const propProperties = getPropProperties(propsTypeName, types);
|
||||
const propProperties = getProperties(propsTypeName, types);
|
||||
const commandOptions = getCommandOptions(commandOptionsExpression);
|
||||
|
||||
const commandProperties = getCommandProperties(
|
||||
@@ -209,7 +236,7 @@ function buildComponentSchema(ast): ComponentSchemaBuilderConfig {
|
||||
const events = getEvents(propProperties, types);
|
||||
const commands = getCommands(commandProperties, types);
|
||||
|
||||
return {
|
||||
const toRet = {
|
||||
filename: componentName,
|
||||
componentName,
|
||||
options,
|
||||
@@ -218,6 +245,17 @@ function buildComponentSchema(ast): ComponentSchemaBuilderConfig {
|
||||
props,
|
||||
commands,
|
||||
};
|
||||
|
||||
if (stateTypeName) {
|
||||
const stateProperties = getProperties(stateTypeName, types);
|
||||
const state = getState(stateProperties, types);
|
||||
return {
|
||||
...toRet,
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
return toRet;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
+11
-436
@@ -9,409 +9,28 @@
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
import type {ASTNode} from '../utils';
|
||||
|
||||
const {getValueFromTypes} = require('../utils.js');
|
||||
const {
|
||||
flattenProperties,
|
||||
getSchemaInfo,
|
||||
getTypeAnnotation,
|
||||
} = require('./componentsUtils.js');
|
||||
|
||||
import type {NamedShape, PropTypeAnnotation} from '../../../CodegenSchema.js';
|
||||
import type {TypeDeclarationMap} from '../utils.js';
|
||||
|
||||
function getPropProperties(
|
||||
propsTypeName: string,
|
||||
types: TypeDeclarationMap,
|
||||
): $FlowFixMe {
|
||||
const typeAlias = types[propsTypeName];
|
||||
try {
|
||||
return typeAlias.right.typeParameters.params[0].properties;
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Failed to find type definition for "${propsTypeName}", please check that you have a valid codegen flow file`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeAnnotationForArray(
|
||||
name: string,
|
||||
typeAnnotation: $FlowFixMe,
|
||||
defaultValue: $FlowFixMe | null,
|
||||
types: TypeDeclarationMap,
|
||||
) {
|
||||
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' &&
|
||||
extractedTypeAnnotation.id.name === '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,
|
||||
)
|
||||
.map(prop => buildPropSchema(prop, types))
|
||||
.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,
|
||||
)
|
||||
.map(prop => buildPropSchema(prop, types))
|
||||
.filter(Boolean),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const type =
|
||||
extractedTypeAnnotation.type === 'GenericTypeAnnotation'
|
||||
? extractedTypeAnnotation.id.name
|
||||
: extractedTypeAnnotation.type;
|
||||
|
||||
switch (type) {
|
||||
case 'ImageSource':
|
||||
return {
|
||||
type: 'ReservedPropTypeAnnotation',
|
||||
name: 'ImageSourcePrimitive',
|
||||
};
|
||||
case 'ColorValue':
|
||||
case 'ProcessedColorValue':
|
||||
return {
|
||||
type: 'ReservedPropTypeAnnotation',
|
||||
name: 'ColorPrimitive',
|
||||
};
|
||||
case 'PointValue':
|
||||
return {
|
||||
type: 'ReservedPropTypeAnnotation',
|
||||
name: 'PointPrimitive',
|
||||
};
|
||||
case 'EdgeInsetsValue':
|
||||
return {
|
||||
type: 'ReservedPropTypeAnnotation',
|
||||
name: 'EdgeInsetsPrimitive',
|
||||
};
|
||||
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 '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:
|
||||
(type: empty);
|
||||
throw new Error(`Unknown prop type for "${name}": ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function getTypeAnnotation(
|
||||
name: string,
|
||||
annotation: $FlowFixMe | ASTNode,
|
||||
defaultValue: $FlowFixMe | null,
|
||||
withNullDefault: boolean,
|
||||
types: TypeDeclarationMap,
|
||||
) {
|
||||
const typeAnnotation = getValueFromTypes(annotation, types);
|
||||
|
||||
if (
|
||||
typeAnnotation.type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === '$ReadOnlyArray'
|
||||
) {
|
||||
return {
|
||||
type: 'ArrayTypeAnnotation',
|
||||
elementType: getTypeAnnotationForArray(
|
||||
name,
|
||||
typeAnnotation.typeParameters.params[0],
|
||||
defaultValue,
|
||||
types,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
typeAnnotation.type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === '$ReadOnly'
|
||||
) {
|
||||
return {
|
||||
type: 'ObjectTypeAnnotation',
|
||||
properties: flattenProperties(
|
||||
typeAnnotation.typeParameters.params[0].properties,
|
||||
types,
|
||||
)
|
||||
.map(prop => buildPropSchema(prop, types))
|
||||
.filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
const type =
|
||||
typeAnnotation.type === 'GenericTypeAnnotation'
|
||||
? typeAnnotation.id.name
|
||||
: typeAnnotation.type;
|
||||
|
||||
switch (type) {
|
||||
case 'ImageSource':
|
||||
return {
|
||||
type: 'ReservedPropTypeAnnotation',
|
||||
name: 'ImageSourcePrimitive',
|
||||
};
|
||||
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 '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`,
|
||||
);
|
||||
default:
|
||||
(type: empty);
|
||||
throw new Error(`Unknown prop type for "${name}": "${type}"`);
|
||||
}
|
||||
}
|
||||
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
|
||||
type PropAST = Object;
|
||||
|
||||
function buildPropSchema(
|
||||
property: PropAST,
|
||||
types: TypeDeclarationMap,
|
||||
): ?NamedShape<PropTypeAnnotation> {
|
||||
const name = property.key.name;
|
||||
|
||||
const value = getValueFromTypes(property.value, types);
|
||||
let typeAnnotation =
|
||||
value.type === 'NullableTypeAnnotation' ? value.typeAnnotation : value;
|
||||
|
||||
const optional =
|
||||
value.type === 'NullableTypeAnnotation' ||
|
||||
property.optional ||
|
||||
(value.type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === 'WithDefault');
|
||||
|
||||
if (
|
||||
!property.optional &&
|
||||
value.type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === 'WithDefault'
|
||||
) {
|
||||
throw new Error(
|
||||
`key ${name} must be optional if used with WithDefault<> annotation`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
value.type === 'NullableTypeAnnotation' &&
|
||||
typeAnnotation.type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === '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' &&
|
||||
(typeAnnotation.id.name === 'DirectEventHandler' ||
|
||||
typeAnnotation.id.name === 'BubblingEventHandler')
|
||||
) {
|
||||
const info = getSchemaInfo(property, types);
|
||||
if (info == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
name === 'style' &&
|
||||
type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === 'ViewStyleProp'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let defaultValue = null;
|
||||
let withNullDefault = false;
|
||||
if (
|
||||
type === 'GenericTypeAnnotation' &&
|
||||
typeAnnotation.id.name === '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'
|
||||
? typeAnnotation.id.name
|
||||
: typeAnnotation.type;
|
||||
|
||||
if (defaultValueType === 'NullLiteralTypeAnnotation') {
|
||||
defaultValue = null;
|
||||
withNullDefault = true;
|
||||
}
|
||||
}
|
||||
const {name, optional, typeAnnotation, defaultValue, withNullDefault} = info;
|
||||
|
||||
return {
|
||||
name,
|
||||
@@ -422,54 +41,11 @@ function buildPropSchema(
|
||||
defaultValue,
|
||||
withNullDefault,
|
||||
types,
|
||||
buildPropSchema,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
|
||||
type PropAST = Object;
|
||||
|
||||
function verifyPropNotAlreadyDefined(
|
||||
props: $ReadOnlyArray<PropAST>,
|
||||
needleProp: PropAST,
|
||||
) {
|
||||
const propName = needleProp.key.name;
|
||||
const foundProp = props.some(prop => prop.key.name === propName);
|
||||
if (foundProp) {
|
||||
throw new Error(`A prop was already defined with the name ${propName}`);
|
||||
}
|
||||
}
|
||||
|
||||
function flattenProperties(
|
||||
typeDefinition: $ReadOnlyArray<PropAST>,
|
||||
types: TypeDeclarationMap,
|
||||
) {
|
||||
return typeDefinition
|
||||
.map(property => {
|
||||
if (property.type === 'ObjectTypeProperty') {
|
||||
return property;
|
||||
} else if (property.type === 'ObjectTypeSpreadProperty') {
|
||||
return flattenProperties(
|
||||
getPropProperties(property.argument.id.name, types),
|
||||
types,
|
||||
);
|
||||
}
|
||||
})
|
||||
.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);
|
||||
}
|
||||
|
||||
function getProps(
|
||||
typeDefinition: $ReadOnlyArray<PropAST>,
|
||||
types: TypeDeclarationMap,
|
||||
@@ -481,5 +57,4 @@ function getProps(
|
||||
|
||||
module.exports = {
|
||||
getProps,
|
||||
getPropProperties,
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
NamedShape,
|
||||
CommandTypeAnnotation,
|
||||
PropTypeAnnotation,
|
||||
StateTypeAnnotation,
|
||||
ExtendsPropsShape,
|
||||
SchemaType,
|
||||
OptionsShape,
|
||||
@@ -27,6 +28,7 @@ export type ComponentSchemaBuilderConfig = $ReadOnly<{
|
||||
events: $ReadOnlyArray<EventTypeShape>,
|
||||
props: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
|
||||
commands: $ReadOnlyArray<NamedShape<CommandTypeAnnotation>>,
|
||||
state?: $ReadOnlyArray<NamedShape<StateTypeAnnotation>>,
|
||||
options?: ?OptionsShape,
|
||||
}>;
|
||||
|
||||
@@ -36,6 +38,7 @@ function wrapComponentSchema({
|
||||
extendsProps,
|
||||
events,
|
||||
props,
|
||||
state,
|
||||
options,
|
||||
commands,
|
||||
}: ComponentSchemaBuilderConfig): SchemaType {
|
||||
@@ -50,6 +53,7 @@ function wrapComponentSchema({
|
||||
events,
|
||||
props,
|
||||
commands,
|
||||
state,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const {
|
||||
flattenProperties,
|
||||
getSchemaInfo,
|
||||
getTypeAnnotation,
|
||||
} = require('./componentsUtils.js');
|
||||
|
||||
import type {StateTypeAnnotation, NamedShape} from '../../../CodegenSchema.js';
|
||||
import type {TypeDeclarationMap} from '../utils.js';
|
||||
|
||||
// $FlowFixMe[unclear-type] there's no flowtype for ASTs
|
||||
type PropAST = Object;
|
||||
|
||||
function buildStateSchema(
|
||||
property: PropAST,
|
||||
types: TypeDeclarationMap,
|
||||
): ?NamedShape<StateTypeAnnotation> {
|
||||
const info = getSchemaInfo(property, types);
|
||||
if (info == null) {
|
||||
return null;
|
||||
}
|
||||
const {name, optional, typeAnnotation, defaultValue, withNullDefault} = info;
|
||||
|
||||
return {
|
||||
name,
|
||||
optional,
|
||||
typeAnnotation: getTypeAnnotation(
|
||||
name,
|
||||
typeAnnotation,
|
||||
defaultValue,
|
||||
withNullDefault,
|
||||
types,
|
||||
buildStateSchema,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function getState(
|
||||
typeDefinition: $ReadOnlyArray<PropAST>,
|
||||
types: TypeDeclarationMap,
|
||||
): $ReadOnlyArray<NamedShape<StateTypeAnnotation>> {
|
||||
return flattenProperties(typeDefinition, types)
|
||||
.map(property => buildStateSchema(property, types))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getState,
|
||||
};
|
||||
Reference in New Issue
Block a user