UNPKG

@react-native/codegen

Version:
618 lines (612 loc) • 19.1 kB
/** * 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 */ 'use strict'; function _defineProperty(e, r, t) { return ( (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0, }) : (e[r] = t), e ); } function _toPropertyKey(t) { var i = _toPrimitive(t, 'string'); return 'symbol' == typeof i ? i : i + ''; } function _toPrimitive(t, r) { if ('object' != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || 'default'); if ('object' != typeof i) return i; throw new TypeError('@@toPrimitive must return a primitive value.'); } return ('string' === r ? String : Number)(t); } const { UnsupportedObjectPropertyTypeAnnotationParserError, } = require('../errors'); const { buildModuleSchema, buildPropSchema, buildSchema, extendsForProp, handleGenericTypeAnnotation, } = require('../parsers-commons.js'); const {Visitor} = require('../parsers-primitives'); const {wrapComponentSchema} = require('../schema.js'); const {buildComponentSchema} = require('./components'); const { flattenProperties, getSchemaInfo, getTypeAnnotation, } = require('./components/componentsUtils'); const {typeScriptTranslateTypeAnnotation} = require('./modules'); const {parseTopLevelType} = require('./parseTopLevelType'); // $FlowFixMe[untyped-import] Use flow-types for @babel/parser const babelParser = require('@babel/parser'); const fs = require('fs'); const invariant = require('invariant'); class TypeScriptParser { constructor() { _defineProperty( this, 'typeParameterInstantiation', 'TSTypeParameterInstantiation', ); _defineProperty(this, 'typeAlias', 'TSTypeAliasDeclaration'); _defineProperty(this, 'enumDeclaration', 'TSEnumDeclaration'); _defineProperty(this, 'interfaceDeclaration', 'TSInterfaceDeclaration'); _defineProperty(this, 'nullLiteralTypeAnnotation', 'TSNullKeyword'); _defineProperty( this, 'undefinedLiteralTypeAnnotation', 'TSUndefinedKeyword', ); } isProperty(property) { return property.type === 'TSPropertySignature'; } getKeyName(property, hasteModuleName) { if (!this.isProperty(property)) { throw new UnsupportedObjectPropertyTypeAnnotationParserError( hasteModuleName, property, property.type, this.language(), ); } return property.key.name; } language() { return 'TypeScript'; } getTypeAnnotationName(typeAnnotation) { var _typeAnnotation$typeN, _typeAnnotation$typeN2; if ( (typeAnnotation === null || typeAnnotation === void 0 || (_typeAnnotation$typeN = typeAnnotation.typeName) === null || _typeAnnotation$typeN === void 0 ? void 0 : _typeAnnotation$typeN.type) === 'TSQualifiedName' ) { return typeAnnotation.typeName.right.name; } return typeAnnotation === null || typeAnnotation === void 0 || (_typeAnnotation$typeN2 = typeAnnotation.typeName) === null || _typeAnnotation$typeN2 === void 0 ? void 0 : _typeAnnotation$typeN2.name; } checkIfInvalidModule(typeArguments) { return ( typeArguments.type !== 'TSTypeParameterInstantiation' || typeArguments.params.length !== 1 || typeArguments.params[0].type !== 'TSTypeReference' || typeArguments.params[0].typeName.name !== 'Spec' ); } remapUnionTypeAnnotationMemberNames(membersTypes) { const remapLiteral = item => { return item.literal ? item.literal.type .replace('NumericLiteral', 'NumberTypeAnnotation') .replace('StringLiteral', 'StringTypeAnnotation') : 'ObjectTypeAnnotation'; }; return [...new Set(membersTypes.map(remapLiteral))]; } getStringLiteralUnionTypeAnnotationStringLiterals(membersTypes) { return membersTypes.map(item => item.literal.value); } parseFile(filename) { const contents = fs.readFileSync(filename, 'utf8'); return this.parseString(contents, filename); } parseString(contents, filename) { return buildSchema( contents, filename, wrapComponentSchema, buildComponentSchema, buildModuleSchema, Visitor, this, typeScriptTranslateTypeAnnotation, ); } parseModuleFixture(filename) { const contents = fs.readFileSync(filename, 'utf8'); return this.parseString(contents, 'path/NativeSampleTurboModule.ts'); } getAst(contents, filename) { return babelParser.parse(contents, { sourceType: 'module', plugins: ['typescript'], }).program; } getFunctionTypeAnnotationParameters(functionTypeAnnotation) { return functionTypeAnnotation.parameters; } getFunctionNameFromParameter(parameter) { return parameter.typeAnnotation; } getParameterName(parameter) { return parameter.name; } getParameterTypeAnnotation(parameter) { return parameter.typeAnnotation.typeAnnotation; } getFunctionTypeAnnotationReturnType(functionTypeAnnotation) { return functionTypeAnnotation.typeAnnotation.typeAnnotation; } parseEnumMembersType(typeAnnotation) { var _typeAnnotation$membe; const enumInitializer = (_typeAnnotation$membe = typeAnnotation.members[0]) === null || _typeAnnotation$membe === void 0 ? void 0 : _typeAnnotation$membe.initializer; const enumInitializerType = enumInitializer === null || enumInitializer === void 0 ? void 0 : enumInitializer.type; let enumMembersType = null; if (!enumInitializerType) { return 'StringTypeAnnotation'; } switch (enumInitializerType) { case 'StringLiteral': enumMembersType = 'StringTypeAnnotation'; break; case 'NumericLiteral': enumMembersType = 'NumberTypeAnnotation'; break; case 'UnaryExpression': if (enumInitializer.operator === '-') { enumMembersType = 'NumberTypeAnnotation'; } break; default: enumMembersType = null; } if (!enumMembersType) { throw new Error( 'Enum values must be either blank, number, or string values.', ); } return enumMembersType; } validateEnumMembersSupported(typeAnnotation, enumMembersType) { if (!typeAnnotation.members || typeAnnotation.members.length === 0) { throw new Error('Enums should have at least one member.'); } const enumInitializerType = enumMembersType === 'StringTypeAnnotation' ? 'StringLiteral' : enumMembersType === 'NumberTypeAnnotation' ? 'NumericLiteral' : null; typeAnnotation.members.forEach(member => { var _member$initializer, _member$initializer2, _member$initializer3, _member$initializer4; const isNegative = ((_member$initializer = member.initializer) === null || _member$initializer === void 0 ? void 0 : _member$initializer.type) === 'UnaryExpression' && ((_member$initializer2 = member.initializer) === null || _member$initializer2 === void 0 ? void 0 : _member$initializer2.operator) === '-'; const initializerType = isNegative ? (_member$initializer3 = member.initializer) === null || _member$initializer3 === void 0 || (_member$initializer3 = _member$initializer3.argument) === null || _member$initializer3 === void 0 ? void 0 : _member$initializer3.type : (_member$initializer4 = member.initializer) === null || _member$initializer4 === void 0 ? void 0 : _member$initializer4.type; if ( (initializerType !== null && initializerType !== void 0 ? initializerType : 'StringLiteral') !== enumInitializerType ) { throw new Error( 'Enum values can not be mixed. They all must be either blank, number, or string values.', ); } }); } parseEnumMembers(typeAnnotation) { return typeAnnotation.members.map(member => { var _member$initializer5, _member$initializer6, _member$initializer7, _member$initializer8, _member$initializer9, _member$initializer10; const value = ((_member$initializer5 = member.initializer) === null || _member$initializer5 === void 0 ? void 0 : _member$initializer5.operator) === '-' ? { type: 'NumberLiteralTypeAnnotation', value: -1 * ((_member$initializer6 = member.initializer) === null || _member$initializer6 === void 0 || (_member$initializer6 = _member$initializer6.argument) === null || _member$initializer6 === void 0 ? void 0 : _member$initializer6.value), } : typeof ((_member$initializer7 = member.initializer) === null || _member$initializer7 === void 0 ? void 0 : _member$initializer7.value) === 'number' ? { type: 'NumberLiteralTypeAnnotation', value: (_member$initializer8 = member.initializer) === null || _member$initializer8 === void 0 ? void 0 : _member$initializer8.value, } : typeof ((_member$initializer9 = member.initializer) === null || _member$initializer9 === void 0 ? void 0 : _member$initializer9.value) === 'string' ? { type: 'StringLiteralTypeAnnotation', value: (_member$initializer10 = member.initializer) === null || _member$initializer10 === void 0 ? void 0 : _member$initializer10.value, } : { type: 'StringLiteralTypeAnnotation', value: member.id.name, }; return { name: member.id.name, value, }; }); } isModuleInterface(node) { var _node$extends; return ( node.type === 'TSInterfaceDeclaration' && ((_node$extends = node.extends) === null || _node$extends === void 0 ? void 0 : _node$extends.length) === 1 && node.extends[0].type === 'TSExpressionWithTypeArguments' && node.extends[0].expression.name === 'TurboModule' ); } isGenericTypeAnnotation(type) { return type === 'TSTypeReference'; } extractAnnotatedElement(typeAnnotation, types) { return types[typeAnnotation.typeParameters.params[0].typeName.name]; } /** * TODO(T108222691): Use flow-types for @babel/parser */ getTypes(ast) { return ast.body.reduce((types, node) => { switch (node.type) { case 'ExportNamedDeclaration': { if (node.declaration) { switch (node.declaration.type) { case 'TSTypeAliasDeclaration': case 'TSInterfaceDeclaration': case 'TSEnumDeclaration': { types[node.declaration.id.name] = node.declaration; break; } } } break; } case 'TSTypeAliasDeclaration': case 'TSInterfaceDeclaration': case 'TSEnumDeclaration': { types[node.id.name] = node; break; } } return types; }, {}); } callExpressionTypeParameters(callExpression) { return callExpression.typeParameters || null; } computePartialProperties( properties, hasteModuleName, types, aliasMap, enumMap, tryParse, cxxOnly, ) { return properties.map(prop => { return { name: prop.key.name, optional: true, typeAnnotation: typeScriptTranslateTypeAnnotation( hasteModuleName, prop.typeAnnotation.typeAnnotation, types, aliasMap, enumMap, tryParse, cxxOnly, this, ), }; }); } functionTypeAnnotation(propertyValueType) { return ( propertyValueType === 'TSFunctionType' || propertyValueType === 'TSMethodSignature' ); } getTypeArgumentParamsFromDeclaration(declaration) { return declaration.typeParameters.params; } // This FlowFixMe is supposed to refer to typeArgumentParams and funcArgumentParams of generated AST. getNativeComponentType(typeArgumentParams, funcArgumentParams) { return { propsTypeName: typeArgumentParams[0].typeName.name, componentName: funcArgumentParams[0].value, }; } getAnnotatedElementProperties(annotatedElement) { return annotatedElement.typeAnnotation.members; } bodyProperties(typeAlias) { return typeAlias.body.body; } convertKeywordToTypeAnnotation(keyword) { switch (keyword) { case 'TSArrayType': return 'ArrayTypeAnnotation'; case 'TSBooleanKeyword': return 'BooleanTypeAnnotation'; case 'TSNumberKeyword': return 'NumberTypeAnnotation'; case 'TSVoidKeyword': return 'VoidTypeAnnotation'; case 'TSStringKeyword': return 'StringTypeAnnotation'; case 'TSTypeLiteral': return 'ObjectTypeAnnotation'; case 'TSUnknownKeyword': return 'MixedTypeAnnotation'; } return keyword; } argumentForProp(prop) { return prop.expression; } nameForArgument(prop) { return prop.expression.name; } isOptionalProperty(property) { return property.optional || false; } getGetSchemaInfoFN() { return getSchemaInfo; } getTypeAnnotationFromProperty(property) { return property.typeAnnotation.typeAnnotation; } getGetTypeAnnotationFN() { return getTypeAnnotation; } getResolvedTypeAnnotation( // TODO(T108222691): Use flow-types for @babel/parser typeAnnotation, types, parser, ) { invariant( typeAnnotation != null, 'resolveTypeAnnotation(): typeAnnotation cannot be null', ); let node = typeAnnotation.type === 'TSTypeAnnotation' ? typeAnnotation.typeAnnotation : typeAnnotation; let nullable = false; let typeResolutionStatus = { successful: false, }; for (;;) { const topLevelType = parseTopLevelType(node, parser); nullable = nullable || topLevelType.optional; node = topLevelType.type; if (node.type !== 'TSTypeReference') { break; } const typeAnnotationName = this.getTypeAnnotationName(node); const resolvedTypeAnnotation = types[typeAnnotationName]; if (resolvedTypeAnnotation == null) { break; } const {typeAnnotation: typeAnnotationNode, typeResolutionStatus: status} = handleGenericTypeAnnotation(node, resolvedTypeAnnotation, this); typeResolutionStatus = status; node = typeAnnotationNode; } return { nullable: nullable, typeAnnotation: node, typeResolutionStatus, }; } getResolveTypeAnnotationFN() { return (typeAnnotation, types, parser) => { return this.getResolvedTypeAnnotation(typeAnnotation, types, parser); }; } isEvent(typeAnnotation) { if (typeAnnotation.type !== 'TSTypeReference') { return false; } const eventNames = new Set(['BubblingEventHandler', 'DirectEventHandler']); return eventNames.has(this.getTypeAnnotationName(typeAnnotation)); } isProp(name, typeAnnotation) { if (typeAnnotation.type !== 'TSTypeReference') { return true; } const isStyle = name === 'style' && typeAnnotation.type === 'GenericTypeAnnotation' && this.getTypeAnnotationName(typeAnnotation) === 'ViewStyleProp'; return !isStyle; } getProps(typeDefinition, types) { const extendsProps = []; const componentPropAsts = []; const remaining = []; for (const prop of typeDefinition) { // find extends if (prop.type === 'TSExpressionWithTypeArguments') { const extend = extendsForProp(prop, types, this); if (extend) { extendsProps.push(extend); continue; } } remaining.push(prop); } // find events and props for (const prop of flattenProperties(remaining, types, this)) { const topLevelType = parseTopLevelType( prop.typeAnnotation.typeAnnotation, this, types, ); if ( prop.type === 'TSPropertySignature' && !this.isEvent(topLevelType.type) && this.isProp(prop.key.name, prop) ) { componentPropAsts.push(prop); } } return { props: componentPropAsts .map(property => buildPropSchema(property, types, this)) .filter(Boolean), extendsProps, }; } getProperties(typeName, types) { const alias = types[typeName]; if (!alias) { throw new Error( `Failed to find definition for "${typeName}", please check that you have a valid codegen typescript file`, ); } const aliasKind = alias.type === 'TSInterfaceDeclaration' ? 'interface' : 'type'; try { if (aliasKind === 'interface') { var _alias$extends; return [ ...((_alias$extends = alias.extends) !== null && _alias$extends !== void 0 ? _alias$extends : []), ...alias.body.body, ]; } return ( alias.typeAnnotation.members || alias.typeAnnotation.typeParameters.params[0].members || alias.typeAnnotation.typeParameters.params ); } catch (e) { throw new Error( `Failed to find ${aliasKind} definition for "${typeName}", please check that you have a valid codegen typescript file`, ); } } nextNodeForTypeAlias(typeAnnotation) { return typeAnnotation.typeAnnotation; } nextNodeForEnum(typeAnnotation) { return typeAnnotation; } genericTypeAnnotationErrorMessage(typeAnnotation) { return `A non GenericTypeAnnotation must be a type declaration ('${this.typeAlias}'), an interface ('${this.interfaceDeclaration}'), or enum ('${this.enumDeclaration}'). Instead, got the unsupported ${typeAnnotation.type}.`; } extractTypeFromTypeAnnotation(typeAnnotation) { return typeAnnotation.type === 'TSTypeReference' ? typeAnnotation.typeName.name : typeAnnotation.type; } getObjectProperties(typeAnnotation) { return typeAnnotation.members; } getLiteralValue(option) { return option.literal.value; } getPaperTopLevelNameDeprecated(typeAnnotation) { return typeAnnotation.typeParameters.params.length > 1 ? typeAnnotation.typeParameters.params[1].literal.value : null; } } module.exports = { TypeScriptParser, };