nitro-codegen
Version:
The code-generator for react-native-nitro-modules.
154 lines (153 loc) • 5.88 kB
JavaScript
import { Node, Symbol } from 'ts-morph';
import { getBaseTypes } from './utils.js';
const platformLanguages = {
ios: ['swift', 'c++'],
android: ['kotlin', 'c++'],
};
const allPlatforms = Object.keys(platformLanguages);
const allLanguages = Object.values(platformLanguages).flatMap((l) => l);
function isValidLanguage(language) {
if (language == null) {
return false;
}
return allLanguages.includes(language);
}
function isValidPlatform(platform) {
return allPlatforms.includes(platform);
}
function getLiteralValue(symbol) {
const value = symbol.getValueDeclaration();
if (value == null) {
return undefined;
}
const type = value.getType();
const literal = type.getLiteralValue();
if (typeof literal === 'string') {
return literal;
}
return undefined;
}
// TODO: The type casting result here doesn't really work in TS.
function isValidLanguageForPlatform(language, platform) {
return platformLanguages[platform].includes(language);
}
function getPlatformSpec(typeName, platformSpecs) {
const result = {};
// Properties (ios, android)
const properties = platformSpecs.getProperties();
for (const property of properties) {
// Property name (ios, android)
const platform = property.getName();
if (!isValidPlatform(platform)) {
console.warn(` ⚠️ ${typeName} does not properly extend HybridObject<T> - "${platform}" is not a valid Platform! ` +
`Valid platforms are: [${allPlatforms.join(', ')}]`);
continue;
}
// Value (swift, kotlin, c++)
const language = getLiteralValue(property);
if (!isValidLanguage(language)) {
console.warn(` ⚠️ ${typeName}: Language ${language} is not a valid language for ${platform}! ` +
`Valid languages are: [${platformLanguages[platform].join(', ')}]`);
continue;
}
// Double-check that language works on this platform (android: kotlin/c++, ios: swift/c++)
if (!isValidLanguageForPlatform(language, platform)) {
console.warn(` ⚠️ ${typeName}: Language ${language} is not a valid language for ${platform}! ` +
`Valid languages are: [${platformLanguages[platform].join(', ')}]`);
continue;
}
// @ts-expect-error because TypeScript isn't smart enough yet to correctly cast after the `isValidLanguageForPlatform` check.
result[platform] = language;
}
return result;
}
function isDirectlyType(type, name) {
const symbol = type.getSymbol() ?? type.getAliasSymbol();
if (symbol?.getName() === name) {
return true;
}
return false;
}
function extendsType(type, name, recursive) {
for (const base of getBaseTypes(type)) {
const isHybrid = isDirectlyType(base, name);
if (isHybrid) {
return true;
}
if (recursive) {
const baseExtends = extendsType(base, name, recursive);
if (baseExtends) {
return true;
}
}
}
return false;
}
export function isDirectlyHybridObject(type) {
return isDirectlyType(type, 'HybridObject');
}
export function extendsHybridObject(type, recursive) {
return extendsType(type, 'HybridObject', recursive);
}
export function isHybridViewProps(type) {
return extendsType(type, 'HybridViewProps', true);
}
export function isHybridViewMethods(type) {
return extendsType(type, 'HybridViewMethods', true);
}
export function isHybridView(type) {
// HybridViews are type aliases for `HybridView`, and `Props & Methods` are just intersected together.
const unionTypes = type.getIntersectionTypes();
for (const union of unionTypes) {
const symbol = union.getSymbol();
if (symbol == null)
return false;
return symbol.getName() === 'HybridViewTag';
}
return false;
}
export function isAnyHybridSubclass(type) {
if (isDirectlyHybridObject(type))
return false;
if (isHybridView(type))
return true;
if (extendsHybridObject(type, true))
return true;
return false;
}
/**
* If the given interface ({@linkcode declaration}) extends `HybridObject`,
* this method returns the platforms it exists on.
* If it doesn't extend `HybridObject`, this returns `undefined`.
*/
export function getHybridObjectPlatforms(declaration) {
const base = getBaseTypes(declaration.getType()).find((t) => isDirectlyHybridObject(t));
if (base == null) {
// this type does not extend `HybridObject`.
throw new Error(`Couldn't find HybridObject<..> base for ${declaration.getName()}! (${declaration.getText()})`);
}
const genericArguments = base.getTypeArguments();
const platformSpecsArgument = genericArguments[0];
if (platformSpecsArgument == null) {
// it uses `HybridObject` without generic arguments. This defaults to C++
return { android: 'c++', ios: 'c++' };
}
return getPlatformSpec(declaration.getName(), platformSpecsArgument);
}
export function getHybridViewPlatforms(view) {
if (Node.isTypeAliasDeclaration(view)) {
const hybridViewTypeNode = view.getTypeNode();
const isHybridViewType = Node.isTypeReference(hybridViewTypeNode) &&
hybridViewTypeNode.getTypeName().getText() === 'HybridView';
if (!isHybridViewType) {
return;
}
const genericArguments = hybridViewTypeNode.getTypeArguments();
const platformSpecArg = genericArguments[2];
if (platformSpecArg != null) {
return getPlatformSpec(view.getName(), platformSpecArg.getType());
}
}
// it uses `HybridObject` without generic arguments. This defaults to platform native languages
return { ios: 'swift', android: 'kotlin' };
}