UNPKG

create-expo-cljs-app

Version:

Create a react native application with Expo and Shadow-CLJS!

748 lines (676 loc) 21.8 kB
/** * 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 */ 'use strict'; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function(sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function(key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function(key) { Object.defineProperty( target, key, Object.getOwnPropertyDescriptor(source, key), ); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true, }); } else { obj[key] = value; } return obj; } function _slicedToArray(arr, i) { return ( _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest() ); } function _nonIterableRest() { throw new TypeError( 'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.', ); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === 'string') return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === 'Object' && o.constructor) n = o.constructor.name; if (n === 'Map' || n === 'Set') return Array.from(o); if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _iterableToArrayLimit(arr, i) { if (typeof Symbol === 'undefined' || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for ( var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true ) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return'] != null) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } const _require = require('../utils.js'), resolveTypeAnnotation = _require.resolveTypeAnnotation, getTypes = _require.getTypes; const _require2 = require('./utils'), unwrapNullable = _require2.unwrapNullable, wrapNullable = _require2.wrapNullable; const _require3 = require('./errors.js'), IncorrectlyParameterizedFlowGenericParserError = _require3.IncorrectlyParameterizedFlowGenericParserError, MisnamedModuleFlowInterfaceParserError = _require3.MisnamedModuleFlowInterfaceParserError, ModuleFlowInterfaceNotParserError = _require3.ModuleFlowInterfaceNotParserError, UnnamedFunctionParamParserError = _require3.UnnamedFunctionParamParserError, UnsupportedArrayElementTypeAnnotationParserError = _require3.UnsupportedArrayElementTypeAnnotationParserError, UnsupportedFlowGenericParserError = _require3.UnsupportedFlowGenericParserError, UnsupportedFlowTypeAnnotationParserError = _require3.UnsupportedFlowTypeAnnotationParserError, UnsupportedFunctionParamTypeAnnotationParserError = _require3.UnsupportedFunctionParamTypeAnnotationParserError, UnsupportedFunctionReturnTypeAnnotationParserError = _require3.UnsupportedFunctionReturnTypeAnnotationParserError, UnsupportedModulePropertyParserError = _require3.UnsupportedModulePropertyParserError, UnsupportedObjectPropertyTypeAnnotationParserError = _require3.UnsupportedObjectPropertyTypeAnnotationParserError, UnsupportedObjectPropertyValueTypeAnnotationParserError = _require3.UnsupportedObjectPropertyValueTypeAnnotationParserError; const invariant = require('invariant'); function nullGuard(fn) { return fn(); } function translateTypeAnnotation( hasteModuleName, /** * TODO(T71778680): Flow-type this node. */ flowTypeAnnotation, types, aliasMap, guard, ) { const _resolveTypeAnnotatio = resolveTypeAnnotation( flowTypeAnnotation, types, ), nullable = _resolveTypeAnnotatio.nullable, typeAnnotation = _resolveTypeAnnotatio.typeAnnotation, typeAliasResolutionStatus = _resolveTypeAnnotatio.typeAliasResolutionStatus; switch (typeAnnotation.type) { case 'GenericTypeAnnotation': { switch (typeAnnotation.id.name) { case 'RootTag': { return wrapNullable(nullable, { type: 'ReservedTypeAnnotation', name: 'RootTag', }); } case 'Promise': { assertGenericTypeAnnotationHasExactlyOneTypeParameter( hasteModuleName, typeAnnotation, ); return wrapNullable(nullable, { type: 'PromiseTypeAnnotation', }); } case 'Array': case '$ReadOnlyArray': { assertGenericTypeAnnotationHasExactlyOneTypeParameter( hasteModuleName, typeAnnotation, ); try { /** * TODO(T72031674): Migrate all our NativeModule specs to not use * invalid Array ElementTypes. Then, make the elementType a required * parameter. */ const _unwrapNullable = unwrapNullable( translateTypeAnnotation( hasteModuleName, typeAnnotation.typeParameters.params[0], types, aliasMap, /** * TODO(T72031674): Ensure that all ParsingErrors that are thrown * while parsing the array element don't get captured and collected. * Why? If we detect any parsing error while parsing the element, * we should default it to null down the line, here. This is * the correct behaviour until we migrate all our NativeModule specs * to be parseable. */ nullGuard, ), ), _unwrapNullable2 = _slicedToArray(_unwrapNullable, 2), elementType = _unwrapNullable2[0], isElementTypeNullable = _unwrapNullable2[1]; if (elementType.type === 'VoidTypeAnnotation') { throw new UnsupportedArrayElementTypeAnnotationParserError( hasteModuleName, typeAnnotation.typeParameters.params[0], typeAnnotation.type, 'void', ); } if (elementType.type === 'PromiseTypeAnnotation') { throw new UnsupportedArrayElementTypeAnnotationParserError( hasteModuleName, typeAnnotation.typeParameters.params[0], typeAnnotation.type, 'Promise', ); } if (elementType.type === 'FunctionTypeAnnotation') { throw new UnsupportedArrayElementTypeAnnotationParserError( hasteModuleName, typeAnnotation.typeParameters.params[0], typeAnnotation.type, 'FunctionTypeAnnotation', ); } const finalTypeAnnotation = { type: 'ArrayTypeAnnotation', elementType: wrapNullable(isElementTypeNullable, elementType), }; return wrapNullable(nullable, finalTypeAnnotation); } catch (ex) { return wrapNullable(nullable, { type: 'ArrayTypeAnnotation', }); } } case '$ReadOnly': { assertGenericTypeAnnotationHasExactlyOneTypeParameter( hasteModuleName, typeAnnotation, ); return translateTypeAnnotation( hasteModuleName, typeAnnotation.typeParameters.params[0], types, aliasMap, guard, ); } case 'Stringish': { return wrapNullable(nullable, { type: 'StringTypeAnnotation', }); } case 'Int32': { return wrapNullable(nullable, { type: 'Int32TypeAnnotation', }); } case 'Double': { return wrapNullable(nullable, { type: 'DoubleTypeAnnotation', }); } case 'Float': { return wrapNullable(nullable, { type: 'FloatTypeAnnotation', }); } case 'Object': { return wrapNullable(nullable, { type: 'GenericObjectTypeAnnotation', }); } default: { throw new UnsupportedFlowGenericParserError( hasteModuleName, typeAnnotation, ); } } } case 'ObjectTypeAnnotation': { const objectTypeAnnotation = { type: 'ObjectTypeAnnotation', properties: typeAnnotation.properties .map(property => { return guard(() => { if (property.type !== 'ObjectTypeProperty') { throw new UnsupportedObjectPropertyTypeAnnotationParserError( hasteModuleName, property, property.type, ); } const optional = property.optional, key = property.key; const _unwrapNullable3 = unwrapNullable( translateTypeAnnotation( hasteModuleName, property.value, types, aliasMap, guard, ), ), _unwrapNullable4 = _slicedToArray(_unwrapNullable3, 2), propertyTypeAnnotation = _unwrapNullable4[0], isPropertyNullable = _unwrapNullable4[1]; if (propertyTypeAnnotation.type === 'FunctionTypeAnnotation') { throw new UnsupportedObjectPropertyValueTypeAnnotationParserError( hasteModuleName, property.value, property.key, propertyTypeAnnotation.type, ); } if (propertyTypeAnnotation.type === 'VoidTypeAnnotation') { throw new UnsupportedObjectPropertyValueTypeAnnotationParserError( hasteModuleName, property.value, property.key, 'void', ); } if (propertyTypeAnnotation.type === 'PromiseTypeAnnotation') { throw new UnsupportedObjectPropertyValueTypeAnnotationParserError( hasteModuleName, property.value, property.key, 'Promise', ); } return { name: key.name, optional, typeAnnotation: wrapNullable( isPropertyNullable, propertyTypeAnnotation, ), }; }); }) .filter(Boolean), }; if (!typeAliasResolutionStatus.successful) { return wrapNullable(nullable, objectTypeAnnotation); } /** * All aliases RHS are required. */ aliasMap[typeAliasResolutionStatus.aliasName] = objectTypeAnnotation; /** * Nullability of type aliases is transitive. * * Consider this case: * * type Animal = ?{ * name: string, * }; * * type B = Animal * * export interface Spec extends TurboModule { * +greet: (animal: B) => void; * } * * In this case, we follow B to Animal, and then Animal to ?{name: string}. * * We: * 1. Replace `+greet: (animal: B) => void;` with `+greet: (animal: ?Animal) => void;`, * 2. Pretend that Animal = {name: string}. * * Why do we do this? * 1. In ObjC, we need to generate a struct called Animal, not B. * 2. This design is simpler than managing nullability within both the type alias usage, and the type alias RHS. * 3. What does it mean for a C++ struct, which is what this type alias RHS will generate, to be nullable? ¯\_(ツ)_/¯ * Nullability is a concept that only makes sense when talking about instances (i.e: usages) of the C++ structs. * Hence, it's better to manage nullability within the actual TypeAliasTypeAnnotation nodes, and not the * associated ObjectTypeAnnotations. */ return wrapNullable(nullable, { type: 'TypeAliasTypeAnnotation', name: typeAliasResolutionStatus.aliasName, }); } case 'BooleanTypeAnnotation': { return wrapNullable(nullable, { type: 'BooleanTypeAnnotation', }); } case 'NumberTypeAnnotation': { return wrapNullable(nullable, { type: 'NumberTypeAnnotation', }); } case 'VoidTypeAnnotation': { return wrapNullable(nullable, { type: 'VoidTypeAnnotation', }); } case 'StringTypeAnnotation': { return wrapNullable(nullable, { type: 'StringTypeAnnotation', }); } case 'FunctionTypeAnnotation': { return wrapNullable( nullable, translateFunctionTypeAnnotation( hasteModuleName, typeAnnotation, types, aliasMap, guard, ), ); } default: { throw new UnsupportedFlowTypeAnnotationParserError( hasteModuleName, typeAnnotation, ); } } } function assertGenericTypeAnnotationHasExactlyOneTypeParameter( moduleName, /** * TODO(T71778680): This is a GenericTypeAnnotation. Flow type this node */ typeAnnotation, ) { if (typeAnnotation.typeParameters == null) { throw new IncorrectlyParameterizedFlowGenericParserError( moduleName, typeAnnotation, ); } invariant( typeAnnotation.typeParameters.type === 'TypeParameterInstantiation', "assertGenericTypeAnnotationHasExactlyOneTypeParameter: Type parameters must be an AST node of type 'TypeParameterInstantiation'", ); if (typeAnnotation.typeParameters.params.length !== 1) { throw new IncorrectlyParameterizedFlowGenericParserError( moduleName, typeAnnotation, ); } } function translateFunctionTypeAnnotation( hasteModuleName, // TODO(T71778680): This is a FunctionTypeAnnotation. Type this. flowFunctionTypeAnnotation, types, aliasMap, guard, ) { const params = []; for (const flowParam of flowFunctionTypeAnnotation.params) { const parsedParam = guard(() => { if (flowParam.name == null) { throw new UnnamedFunctionParamParserError(flowParam, hasteModuleName); } const paramName = flowParam.name.name; const _unwrapNullable5 = unwrapNullable( translateTypeAnnotation( hasteModuleName, flowParam.typeAnnotation, types, aliasMap, guard, ), ), _unwrapNullable6 = _slicedToArray(_unwrapNullable5, 2), paramTypeAnnotation = _unwrapNullable6[0], isParamTypeAnnotationNullable = _unwrapNullable6[1]; if (paramTypeAnnotation.type === 'VoidTypeAnnotation') { throw new UnsupportedFunctionParamTypeAnnotationParserError( hasteModuleName, flowParam.typeAnnotation, paramName, 'void', ); } if (paramTypeAnnotation.type === 'PromiseTypeAnnotation') { throw new UnsupportedFunctionParamTypeAnnotationParserError( hasteModuleName, flowParam.typeAnnotation, paramName, 'Promise', ); } return { name: flowParam.name.name, optional: flowParam.optional, typeAnnotation: wrapNullable( isParamTypeAnnotationNullable, paramTypeAnnotation, ), }; }); if (parsedParam != null) { params.push(parsedParam); } } const _unwrapNullable7 = unwrapNullable( translateTypeAnnotation( hasteModuleName, flowFunctionTypeAnnotation.returnType, types, aliasMap, guard, ), ), _unwrapNullable8 = _slicedToArray(_unwrapNullable7, 2), returnTypeAnnotation = _unwrapNullable8[0], isReturnTypeAnnotationNullable = _unwrapNullable8[1]; if (returnTypeAnnotation.type === 'FunctionTypeAnnotation') { throw new UnsupportedFunctionReturnTypeAnnotationParserError( hasteModuleName, flowFunctionTypeAnnotation.returnType, 'FunctionTypeAnnotation', ); } return { type: 'FunctionTypeAnnotation', returnTypeAnnotation: wrapNullable( isReturnTypeAnnotationNullable, returnTypeAnnotation, ), params, }; } function buildPropertySchema( hasteModuleName, // TODO(T71778680): This is an ObjectTypeProperty containing either: // - a FunctionTypeAnnotation or GenericTypeAnnotation // - a NullableTypeAnnoation containing a FunctionTypeAnnotation or GenericTypeAnnotation // Flow type this node property, types, aliasMap, guard, ) { let nullable = false; let key = property.key, value = property.value; const methodName = key.name; var _resolveTypeAnnotatio2 = resolveTypeAnnotation(value, types); nullable = _resolveTypeAnnotatio2.nullable; value = _resolveTypeAnnotatio2.typeAnnotation; if (value.type !== 'FunctionTypeAnnotation') { throw new UnsupportedModulePropertyParserError( hasteModuleName, property.value, property.key.name, value.type, ); } return { name: methodName, optional: property.optional, typeAnnotation: wrapNullable( nullable, translateFunctionTypeAnnotation( hasteModuleName, value, types, aliasMap, guard, ), ), }; } function buildModuleSchema( hasteModuleName, moduleNames, /** * TODO(T71778680): Flow-type this node. */ ast, guard, ) { const types = getTypes(ast); const moduleInterfaceNames = Object.keys(types).filter(typeName => { const declaration = types[typeName]; return ( declaration.type === 'InterfaceDeclaration' && declaration.extends.length === 1 && declaration.extends[0].type === 'InterfaceExtends' && declaration.extends[0].id.name === 'TurboModule' ); }); if (moduleInterfaceNames.length !== 1) { throw new ModuleFlowInterfaceNotParserError(hasteModuleName, ast); } const _moduleInterfaceNames = _slicedToArray(moduleInterfaceNames, 1), moduleInterfaceName = _moduleInterfaceNames[0]; if (moduleInterfaceName !== 'Spec') { throw new MisnamedModuleFlowInterfaceParserError( hasteModuleName, types[moduleInterfaceName].id, ); } // Some module names use platform suffix to indicate platform-exclusive modules. // Eventually this should be made explicit in the Flow type itself. // Also check the hasteModuleName for platform suffix. // Note: this shape is consistent with ComponentSchema. const excludedPlatforms = []; const namesToValidate = [...moduleNames, hasteModuleName]; namesToValidate.forEach(name => { if (name.endsWith('Android')) { excludedPlatforms.push('iOS'); } else if (name.endsWith('IOS')) { excludedPlatforms.push('android'); } }); const declaration = types[moduleInterfaceName]; return declaration.body.properties .filter(property => property.type === 'ObjectTypeProperty') .map(property => { const aliasMap = {}; return guard(() => ({ aliasMap: aliasMap, propertyShape: buildPropertySchema( hasteModuleName, property, types, aliasMap, guard, ), })); }) .filter(Boolean) .reduce( (moduleSchema, {aliasMap, propertyShape}) => { return { type: 'NativeModule', aliases: _objectSpread( _objectSpread({}, moduleSchema.aliases), aliasMap, ), spec: { properties: [...moduleSchema.spec.properties, propertyShape], }, moduleNames: moduleSchema.moduleNames, excludedPlatforms: moduleSchema.excludedPlatforms, }; }, { type: 'NativeModule', aliases: {}, spec: { properties: [], }, moduleNames: moduleNames, excludedPlatforms: excludedPlatforms.length !== 0 ? [...excludedPlatforms] : undefined, }, ); } module.exports = { buildModuleSchema, };