Add parser support for number unions as Int32 enums

Summary:
## Overview

This diff adds flow parser support for number unions as Int32Enums

The following will be supported as an Int32 enum:

```
interval?: WithDefault<0 | 15 | 30 | 60, 0>,
```

## Number type issues
We assume that all number enums are ints (so far there's not been a valid use case for unions of floats).

If we think there would be a use case for float unions we would need to update this to something like:

```
// Int32
intervalInt?: WithDefault<Int32Enum<0 | 15 | 30 | 60>, 0>,

// Float
intervalInt?: WithDefault<FloatEnum<0.0 | 15.1 | 30.2 | 60.3>, 0.0>,
```

My recommendation is that we default number unions to ints and if a use case arises later for floats, we would add the Float support as:

```
// Int32
intervalInt?: WithDefault<0 | 15 | 30 | 60, 0>,

// Float
intervalInt?: WithDefault<FloatEnum<0.0 | 15.1 | 30.2 | 60.3>, 0.0>,
```

Reviewed By: JoshuaGross

Differential Revision: D17161701

fbshipit-source-id: 4b016eee45bf28bf505afd14a6c1aeea6ca8c04f
This commit is contained in:
Rick Hanlon
2019-09-06 05:33:08 -07:00
committed by Facebook Github Bot
parent e352edebeb
commit 6874bade43
4 changed files with 228 additions and 11 deletions
@@ -432,6 +432,151 @@ export default (codegenNativeComponent<ModuleProps>(
): NativeComponent<ModuleProps>);
`;
const PROP_MIXED_ENUM = `
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
import type {ViewProps} from 'ViewPropTypes';
import type {NativeComponent} from 'codegenNativeComponent';
const codegenNativeComponent = require('codegenNativeComponent');
export type ModuleProps = $ReadOnly<{|
...ViewProps,
someProp?: WithDefault<'foo' | 1, 1>
|}>;
export default (codegenNativeComponent<ModuleProps>(
'Module',
): NativeComponent<ModuleProps>);
`;
const PROP_ENUM_BOOLEAN = `
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
import type {ViewProps} from 'ViewPropTypes';
import type {NativeComponent} from 'codegenNativeComponent';
const codegenNativeComponent = require('codegenNativeComponent');
export type ModuleProps = $ReadOnly<{|
...ViewProps,
someProp?: WithDefault<false | true, false>
|}>;
export default (codegenNativeComponent<ModuleProps>(
'Module',
): NativeComponent<ModuleProps>);
`;
const PROP_ARRAY_MIXED_ENUM = `
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
import type {ViewProps} from 'ViewPropTypes';
import type {NativeComponent} from 'codegenNativeComponent';
const codegenNativeComponent = require('codegenNativeComponent');
export type ModuleProps = $ReadOnly<{|
...ViewProps,
someProp?: WithDefault<$ReadOnlyArray<'foo' | 1>, 1>
|}>;
export default (codegenNativeComponent<ModuleProps>(
'Module',
): NativeComponent<ModuleProps>);
`;
const PROP_ARRAY_ENUM_BOOLEAN = `
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
import type {ViewProps} from 'ViewPropTypes';
import type {NativeComponent} from 'codegenNativeComponent';
const codegenNativeComponent = require('codegenNativeComponent');
export type ModuleProps = $ReadOnly<{|
...ViewProps,
someProp?: WithDefault<$ReadOnlyArray<false | true>, false>
|}>;
export default (codegenNativeComponent<ModuleProps>(
'Module',
): NativeComponent<ModuleProps>);
`;
const PROP_ARRAY_ENUM_INT = `
/**
* Copyright (c) Facebook, Inc. and its 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
*/
'use strict';
import type {ViewProps} from 'ViewPropTypes';
import type {NativeComponent} from 'codegenNativeComponent';
const codegenNativeComponent = require('codegenNativeComponent');
export type ModuleProps = $ReadOnly<{|
...ViewProps,
someProp?: WithDefault<$ReadOnlyArray<0 | 1>, 0>
|}>;
export default (codegenNativeComponent<ModuleProps>(
'Module',
): NativeComponent<ModuleProps>);
`;
module.exports = {
COMMANDS_DEFINED_INLINE,
COMMANDS_DEFINED_MULTIPLE_TIMES,
@@ -445,4 +590,9 @@ module.exports = {
PROPS_CONFLICT_WITH_SPREAD_PROPS,
PROPS_SPREAD_CONFLICTS_WITH_PROPS,
PROP_NUMBER_TYPE,
PROP_MIXED_ENUM,
PROP_ENUM_BOOLEAN,
PROP_ARRAY_MIXED_ENUM,
PROP_ARRAY_ENUM_BOOLEAN,
PROP_ARRAY_ENUM_INT,
};
@@ -242,6 +242,9 @@ type ModuleProps = $ReadOnly<{|
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 |}>,
@@ -16,6 +16,16 @@ exports[`RN Codegen Flow Parser Fails with error message NON_OPTIONAL_KEY_WITH_D
exports[`RN Codegen Flow Parser Fails with error message NULLABLE_WITH_DEFAULT 1`] = `"WithDefault<> is optional and does not need to be marked as optional. Please remove the ? annotation in front of it."`;
exports[`RN Codegen Flow Parser Fails with error message PROP_ARRAY_ENUM_BOOLEAN 1`] = `"Unsupported union type for \\"someProp\\", recieved \\"BooleanLiteralTypeAnnotation\\""`;
exports[`RN Codegen Flow Parser Fails with error message PROP_ARRAY_ENUM_INT 1`] = `"Arrays of int enums are not supported (see: \\"someProp\\")"`;
exports[`RN Codegen Flow Parser Fails with error message PROP_ARRAY_MIXED_ENUM 1`] = `"Mixed types are not supported (see \\"someProp\\")"`;
exports[`RN Codegen Flow Parser Fails with error message PROP_ENUM_BOOLEAN 1`] = `"Unsupported union type for \\"someProp\\", received \\"BooleanLiteralTypeAnnotation\\""`;
exports[`RN Codegen Flow Parser Fails with error message PROP_MIXED_ENUM 1`] = `"Mixed types are not supported (see \\"someProp\\")"`;
exports[`RN Codegen Flow Parser Fails with error message PROP_NUMBER_TYPE 1`] = `"Cannot use \\"NumberTypeAnnotation\\" type annotation for \\"someProp\\": must use a specific numeric type like Int32, Double, or Float"`;
exports[`RN Codegen Flow Parser Fails with error message PROPS_CONFLICT_NAMES 1`] = `"A prop was already defined with the name isEnabled"`;
@@ -247,6 +257,22 @@ Object {
"type": "StringEnumTypeAnnotation",
},
},
Object {
"name": "int_enum_optional_key",
"optional": true,
"typeAnnotation": Object {
"default": 0,
"options": Array [
Object {
"value": 0,
},
Object {
"value": 1,
},
],
"type": "Int32EnumTypeAnnotation",
},
},
Object {
"name": "object_optional_key",
"optional": true,
@@ -106,16 +106,33 @@ function getTypeAnnotationForArray(name, typeAnnotation, defaultValue, types) {
type: 'StringTypeAnnotation',
};
case 'UnionTypeAnnotation':
if (defaultValue == null) {
throw new Error(`A default array enum value is required for "${name}"`);
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 => ({name: 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}", recieved "${unionType}"`,
);
}
return {
type: 'StringEnumTypeAnnotation',
default: defaultValue,
options: extractedTypeAnnotation.types.map(option => ({
name: option.value,
})),
};
default:
(type: empty);
throw new Error(`Unknown prop type for "${name}": ${type}`);
@@ -221,14 +238,35 @@ function getTypeAnnotation(name, annotation, defaultValue, types) {
}
throw new Error(`A default string (or null) is required for "${name}"`);
case 'UnionTypeAnnotation':
if (defaultValue !== null) {
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 => ({name: option.value})),
};
} else if (unionType === 'NumberLiteralTypeAnnotation') {
return {
type: 'Int32EnumTypeAnnotation',
default: (defaultValue: number),
options: typeAnnotation.types.map(option => ({value: option.value})),
};
} else {
throw new Error(
`Unsupported union type for "${name}", received "${unionType}"`,
);
}
throw new Error(`A default enum value is required for "${name}"`);
case 'NumberTypeAnnotation':
throw new Error(
`Cannot use "${type}" type annotation for "${name}": must use a specific numeric type like Int32, Double, or Float`,