nitro-codegen
Version:
The code-generator for react-native-nitro-modules.
204 lines (187 loc) • 5.66 kB
text/typescript
import type { CodeNode } from './CodeNode.js'
import { capitalizeName } from '../utils.js'
import { type SourceFile, type SourceImport } from './SourceFile.js'
import type { Language } from '../getPlatformSpecs.js'
import type { Type } from './types/Type.js'
import { Method } from './Method.js'
import { VoidType } from './types/VoidType.js'
import { Parameter } from './Parameter.js'
import { isBooleanPropertyPrefix } from './helpers.js'
export interface PropertyBody {
getter: string
setter: string
}
export type LanguageEnvironment = 'jvm' | 'swift' | 'other'
export interface PropertyModifiers {
/**
* The name of the class that defines this C++ property getter/setter method.
* Example: `Person` -> `int Person::getAge()`
*/
classDefinitionName?: string
/**
* Whether the property should be marked as inlineable.
*/
inline?: boolean
/*+
* Whether the property is a pure virtual C++ property (getter + setter).
*/
virtual?: boolean
/**
* Whether the property is marked as `noexcept` (doesn't throw) or not.
*/
noexcept?: boolean
/**
* Whether this property overrides a base/super property.
*/
override?: boolean
/**
* Whether this property has a `@DoNotStrip` and `@Keep` attribute to avoid
* it from being stripped from the binary by the Java compiler or ProGuard.
*/
doNotStrip?: boolean
}
export class Property implements CodeNode {
readonly name: string
readonly type: Type
readonly isReadonly: boolean
constructor(name: string, type: Type, isReadonly: boolean) {
this.name = name
this.type = type
this.isReadonly = isReadonly
if (this.name.startsWith('__')) {
throw new Error(
`Property names are not allowed to start with two underscores (__)! (In ${this.jsSignature})`
)
}
}
get jsSignature(): string {
return `${this.name}: ${this.type.kind}`
}
getExtraFiles(): SourceFile[] {
return this.type.getExtraFiles()
}
getRequiredImports(): SourceImport[] {
return this.type.getRequiredImports()
}
getGetterName(environment: LanguageEnvironment): string {
if (this.type.kind === 'boolean' && isBooleanPropertyPrefix(this.name)) {
// Boolean accessors where the property starts with "is" or "has" are renamed in JVM and Swift
switch (environment) {
case 'jvm':
case 'swift':
// isSomething -> isSomething()
return this.name
default:
break
}
}
// isSomething -> getIsSomething()
return `get${capitalizeName(this.name)}`
}
getSetterName(environment: LanguageEnvironment): string {
if (this.type.kind === 'boolean' && this.name.startsWith('is')) {
// Boolean accessors where the property starts with "is" are renamed in JVM
if (environment === 'jvm') {
// isSomething -> setSomething()
const cleanName = this.name.replace('is', '')
return `set${capitalizeName(cleanName)}`
}
}
// isSomething -> setIsSomething()
return `set${capitalizeName(this.name)}`
}
get cppGetter(): Method {
return new Method(this.getGetterName('other'), this.type, [])
}
get cppSetter(): Method | undefined {
if (this.isReadonly) return undefined
const parameter = new Parameter(this.name, this.type)
return new Method(this.getSetterName('other'), new VoidType(), [parameter])
}
getCppMethods(): [getter: Method] | [getter: Method, setter: Method] {
if (this.cppSetter != null) {
// get + set
return [this.cppGetter, this.cppSetter]
} else {
// get
return [this.cppGetter]
}
}
getCode(
language: Language,
modifiers?: PropertyModifiers,
body?: PropertyBody
): string {
if (body != null) {
body.getter = body.getter.trim()
body.setter = body.setter.trim()
}
switch (language) {
case 'c++': {
const methods = this.getCppMethods()
const [getter, setter] = methods
const lines: string[] = []
lines.push(getter.getCode('c++', modifiers, body?.getter))
if (setter != null) {
lines.push(setter.getCode('c++', modifiers, body?.setter))
}
return lines.join('\n')
}
case 'swift': {
const type = this.type.getCode('swift')
let accessors: string
if (body == null) {
accessors = this.isReadonly ? `get` : `get set`
} else {
const lines: string[] = []
lines.push(`
get {
${body.getter}
}
`)
if (!this.isReadonly) {
lines.push(`
set {
${body.setter}
}
`)
}
accessors = '\n' + lines.join('\n') + '\n'
}
return `var ${this.name}: ${type} { ${accessors} }`
}
case 'kotlin': {
const type = this.type.getCode('kotlin')
let keyword = this.isReadonly ? 'val' : 'var'
if (modifiers?.virtual) keyword = `abstract ${keyword}`
const lines: string[] = []
if (modifiers?.doNotStrip) {
lines.push('@get:DoNotStrip', '@get:Keep')
if (!this.isReadonly) {
lines.push('@set:DoNotStrip', '@set:Keep')
}
}
lines.push(`${keyword} ${this.name}: ${type}`)
if (body != null) {
lines.push(`
get() {
${body.getter}
}
`)
if (!this.isReadonly) {
lines.push(`
set(value) {
${body.setter}
}
`)
}
}
return lines.join('\n')
}
default:
throw new Error(
`Language ${language} is not yet supported for properties!`
)
}
}
}