mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
d2f3f06826
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/46827 You can now define a native module that takes an argument that is a specific string literal: Like this: ``` export interface Spec extends TurboModule { +passString: (arg: string) => void; +passStringLiteral: (arg: 'A String Literal') => void; } ``` On the native side, this will still generate `string`, but the schema will now store the string literal, and it will be allowed in JS. This should allow more strict flow / typescript types for modules. This will also allow the compatibility check to fail if the literal changes. This is a step towards accepting a union of string literals. Changelog: [General][Added] Codegen for Native Modules now supports string literals Reviewed By: GijsWeterings Differential Revision: D63872440 fbshipit-source-id: e54b97d34af4a3d1af51727db0777f26fb7b778c
234 lines
6.3 KiB
JavaScript
234 lines
6.3 KiB
JavaScript
/**
|
|
* 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 {
|
|
NativeModuleArrayTypeAnnotation,
|
|
NativeModuleBaseTypeAnnotation,
|
|
BooleanTypeAnnotation,
|
|
DoubleTypeAnnotation,
|
|
NativeModuleEnumDeclaration,
|
|
FloatTypeAnnotation,
|
|
NativeModuleGenericObjectTypeAnnotation,
|
|
Int32TypeAnnotation,
|
|
NativeModuleNumberTypeAnnotation,
|
|
NativeModuleObjectTypeAnnotation,
|
|
StringTypeAnnotation,
|
|
StringLiteralTypeAnnotation,
|
|
NativeModuleTypeAliasTypeAnnotation,
|
|
Nullable,
|
|
ReservedTypeAnnotation,
|
|
} from '../../../CodegenSchema';
|
|
import type {AliasResolver} from '../Utils';
|
|
|
|
const {
|
|
unwrapNullable,
|
|
wrapNullable,
|
|
} = require('../../../parsers/parsers-commons');
|
|
const {capitalize} = require('../../Utils');
|
|
|
|
type StructContext = 'CONSTANTS' | 'REGULAR';
|
|
|
|
export type RegularStruct = $ReadOnly<{
|
|
context: 'REGULAR',
|
|
name: string,
|
|
properties: $ReadOnlyArray<StructProperty>,
|
|
}>;
|
|
|
|
export type ConstantsStruct = $ReadOnly<{
|
|
context: 'CONSTANTS',
|
|
name: string,
|
|
properties: $ReadOnlyArray<StructProperty>,
|
|
}>;
|
|
|
|
export type Struct = RegularStruct | ConstantsStruct;
|
|
|
|
export type StructProperty = $ReadOnly<{
|
|
name: string,
|
|
optional: boolean,
|
|
typeAnnotation: Nullable<StructTypeAnnotation>,
|
|
}>;
|
|
|
|
export type StructTypeAnnotation =
|
|
| StringTypeAnnotation
|
|
| StringLiteralTypeAnnotation
|
|
| NativeModuleNumberTypeAnnotation
|
|
| Int32TypeAnnotation
|
|
| DoubleTypeAnnotation
|
|
| FloatTypeAnnotation
|
|
| BooleanTypeAnnotation
|
|
| NativeModuleEnumDeclaration
|
|
| NativeModuleGenericObjectTypeAnnotation
|
|
| ReservedTypeAnnotation
|
|
| NativeModuleTypeAliasTypeAnnotation
|
|
| NativeModuleArrayTypeAnnotation<Nullable<StructTypeAnnotation>>;
|
|
|
|
class StructCollector {
|
|
_structs: Map<string, Struct> = new Map();
|
|
|
|
process(
|
|
structName: string,
|
|
structContext: StructContext,
|
|
resolveAlias: AliasResolver,
|
|
nullableTypeAnnotation: Nullable<NativeModuleBaseTypeAnnotation>,
|
|
): Nullable<StructTypeAnnotation> {
|
|
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
|
|
switch (typeAnnotation.type) {
|
|
case 'ObjectTypeAnnotation': {
|
|
this._insertStruct(
|
|
structName,
|
|
structContext,
|
|
resolveAlias,
|
|
typeAnnotation,
|
|
);
|
|
return wrapNullable(nullable, {
|
|
type: 'TypeAliasTypeAnnotation',
|
|
name: structName,
|
|
});
|
|
}
|
|
case 'ArrayTypeAnnotation': {
|
|
if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') {
|
|
return wrapNullable(nullable, {
|
|
type: 'ArrayTypeAnnotation',
|
|
elementType: {
|
|
type: 'AnyTypeAnnotation',
|
|
},
|
|
});
|
|
}
|
|
|
|
return wrapNullable(nullable, {
|
|
type: 'ArrayTypeAnnotation',
|
|
elementType: this.process(
|
|
structName + 'Element',
|
|
structContext,
|
|
resolveAlias,
|
|
typeAnnotation.elementType,
|
|
),
|
|
});
|
|
}
|
|
case 'TypeAliasTypeAnnotation': {
|
|
this._insertAlias(typeAnnotation.name, structContext, resolveAlias);
|
|
return wrapNullable(nullable, typeAnnotation);
|
|
}
|
|
case 'EnumDeclaration':
|
|
return wrapNullable(nullable, typeAnnotation);
|
|
case 'MixedTypeAnnotation':
|
|
throw new Error('Mixed types are unsupported in structs');
|
|
case 'UnionTypeAnnotation':
|
|
switch (typeAnnotation.memberType) {
|
|
case 'StringTypeAnnotation':
|
|
return wrapNullable(nullable, {
|
|
type: 'StringTypeAnnotation',
|
|
});
|
|
case 'NumberTypeAnnotation':
|
|
return wrapNullable(nullable, {
|
|
type: 'NumberTypeAnnotation',
|
|
});
|
|
case 'ObjectTypeAnnotation':
|
|
// This isn't smart enough to actually know how to generate the
|
|
// options on the native side. So we just treat it as an unknown object type
|
|
return wrapNullable(nullable, {
|
|
type: 'GenericObjectTypeAnnotation',
|
|
});
|
|
default:
|
|
(typeAnnotation.memberType: empty);
|
|
throw new Error(
|
|
'Union types are unsupported in structs' +
|
|
JSON.stringify(typeAnnotation),
|
|
);
|
|
}
|
|
default: {
|
|
return wrapNullable(nullable, typeAnnotation);
|
|
}
|
|
}
|
|
}
|
|
|
|
_insertAlias(
|
|
aliasName: string,
|
|
structContext: StructContext,
|
|
resolveAlias: AliasResolver,
|
|
): void {
|
|
const usedStruct = this._structs.get(aliasName);
|
|
if (usedStruct == null) {
|
|
this._insertStruct(
|
|
aliasName,
|
|
structContext,
|
|
resolveAlias,
|
|
resolveAlias(aliasName),
|
|
);
|
|
} else if (usedStruct.context !== structContext) {
|
|
throw new Error(
|
|
`Tried to use alias '${aliasName}' in a getConstants() return type and inside a regular struct.`,
|
|
);
|
|
}
|
|
}
|
|
|
|
_insertStruct(
|
|
structName: string,
|
|
structContext: StructContext,
|
|
resolveAlias: AliasResolver,
|
|
objectTypeAnnotation: NativeModuleObjectTypeAnnotation,
|
|
): void {
|
|
// $FlowFixMe[missing-type-arg]
|
|
const properties = objectTypeAnnotation.properties.map<
|
|
$ReadOnly<{
|
|
name: string,
|
|
optional: boolean,
|
|
typeAnnotation: Nullable<StructTypeAnnotation>,
|
|
}>,
|
|
>(property => {
|
|
const propertyStructName = structName + capitalize(property.name);
|
|
|
|
return {
|
|
...property,
|
|
typeAnnotation: this.process(
|
|
propertyStructName,
|
|
structContext,
|
|
resolveAlias,
|
|
property.typeAnnotation,
|
|
),
|
|
};
|
|
});
|
|
|
|
switch (structContext) {
|
|
case 'REGULAR':
|
|
this._structs.set(structName, {
|
|
name: structName,
|
|
context: 'REGULAR',
|
|
properties: properties,
|
|
});
|
|
break;
|
|
case 'CONSTANTS':
|
|
this._structs.set(structName, {
|
|
name: structName,
|
|
context: 'CONSTANTS',
|
|
properties: properties,
|
|
});
|
|
break;
|
|
default:
|
|
(structContext: empty);
|
|
throw new Error(`Detected an invalid struct context: ${structContext}`);
|
|
}
|
|
}
|
|
|
|
getAllStructs(): $ReadOnlyArray<Struct> {
|
|
return [...this._structs.values()];
|
|
}
|
|
|
|
getStruct(name: string): ?Struct {
|
|
return this._structs.get(name);
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
StructCollector,
|
|
};
|