UNPKG

nitro-codegen

Version:

The code-generator for react-native-nitro-modules.

172 lines (155 loc) 5.81 kB
import { Node, Type } from 'ts-morph' import type { SourceFile } from './syntax/SourceFile.js' import { createCppHybridObject } from './syntax/c++/CppHybridObject.js' import { extendsHybridObject, isHybridView, isAnyHybridSubclass, isDirectlyHybridObject, type Language, isHybridViewProps, isHybridViewMethods, } from './getPlatformSpecs.js' import type { HybridObjectSpec } from './syntax/HybridObjectSpec.js' import { Property } from './syntax/Property.js' import { Method } from './syntax/Method.js' import { createSwiftHybridObject } from './syntax/swift/SwiftHybridObject.js' import { createKotlinHybridObject } from './syntax/kotlin/KotlinHybridObject.js' import { createType } from './syntax/createType.js' import { Parameter } from './syntax/Parameter.js' import { getBaseTypes } from './utils.js' export function generatePlatformFiles( interfaceType: Type, language: Language ): SourceFile[] { const spec = getHybridObjectSpec(interfaceType, language) // TODO: We currently just call this so the HybridObject itself is a "known type". // This causes the Swift Umbrella header to properly forward-declare it. // Without this, only Hybrid Objects that are actually used in public APIs will be forward-declared. createType(language, interfaceType, false) switch (language) { case 'c++': return generateCppFiles(spec) case 'swift': return generateSwiftFiles(spec) case 'kotlin': return generateKotlinFiles(spec) default: throw new Error(`Language "${language}" is not supported!`) } } function getHybridObjectSpec(type: Type, language: Language): HybridObjectSpec { if (isHybridView(type)) { const symbol = type.getAliasSymbolOrThrow() const name = symbol.getEscapedName() // It's a Hybrid View - the `Props & Methods` types are just intersected together. const unions = type.getIntersectionTypes() const props = unions.find((t) => isHybridViewProps(t)) const methods = unions.find((t) => isHybridViewMethods(t)) if (props == null) throw new Error( `Props cannot be null! ${name}<...> (HybridView) requires type arguments.` ) const propsSpec = getHybridObjectSpec(props, language) const methodsSpec = methods != null ? getHybridObjectSpec(methods, language) : undefined return { baseTypes: [], isHybridView: true, language: language, methods: methodsSpec?.methods ?? [], properties: propsSpec.properties, name: name, } } const symbol = type.getSymbolOrThrow() const name = symbol.getEscapedName() const properties: Property[] = [] const methods: Method[] = [] for (const prop of type.getProperties()) { const declarations = prop.getDeclarations() if (declarations.length > 1) { throw new Error( `${name}: Function overloading is not supported! (In "${prop.getName()}")` ) } let declaration = declarations[0] if (declaration == null) { throw new Error( `${name}: Property "${prop.getName()}" does not have a type declaration!` ) } const parent = declaration.getParentOrThrow().getType() if (parent === type) { // it's an own property. declared literally here. fine. } else if ( extendsHybridObject(parent, true) || isDirectlyHybridObject(parent) ) { // it's coming from a base class that is already a HybridObject. We can grab this via inheritance. // don't generate this property natively. continue } else { // it's coming from any TypeScript type that is not a HybridObject. // Maybe just a literal interface, then we copy over the props. } if (Node.isPropertySignature(declaration)) { const t = declaration.getType() const propType = createType( language, t, prop.isOptional() || t.isNullable() ) properties.push( new Property(prop.getName(), propType, declaration.isReadonly()) ) } else if (Node.isMethodSignature(declaration)) { const returnType = declaration.getReturnType() const methodReturnType = createType( language, returnType, returnType.isNullable() ) const methodParameters = declaration .getParameters() .map((p) => new Parameter(p, language)) methods.push( new Method(prop.getName(), methodReturnType, methodParameters) ) } else { throw new Error( `${name}: Property "${prop.getName()}" is neither a property, nor a method!` ) } } const bases = getBaseTypes(type) .filter((t) => isAnyHybridSubclass(t)) .map((t) => getHybridObjectSpec(t, language)) const spec: HybridObjectSpec = { language: language, name: name, properties: properties, methods: methods, baseTypes: bases, isHybridView: isHybridView(type), } return spec } function generateCppFiles(spec: HybridObjectSpec): SourceFile[] { const cppFiles = createCppHybridObject(spec) return cppFiles } function generateSwiftFiles(spec: HybridObjectSpec): SourceFile[] { // 1. Always generate a C++ spec for the shared layer and type declarations (enums, interfaces, ...) const cppFiles = generateCppFiles(spec) // 2. Generate Swift specific files and potentially a C++ binding layer const swiftFiles = createSwiftHybridObject(spec) return [...cppFiles, ...swiftFiles] } function generateKotlinFiles(spec: HybridObjectSpec): SourceFile[] { // 1. Always generate a C++ spec for the shared layer and type declarations (enums, interfaces, ...) const cppFiles = generateCppFiles(spec) // 2. Generate Kotlin specific files and potentially a C++ binding layer const kotlinFiles = createKotlinHybridObject(spec) return [...cppFiles, ...kotlinFiles] }