UNPKG

nihilqui

Version:

Typescript .d.ts generator from GIR for gjs and node-gtk

1,467 lines (1,230 loc) 64 kB
import { Generator } from '@ts-for-gir/generator-base' import { Logger, generateIndent, removeNamespace, removeClassModule, girElementIsIntrospectable, typesContainsOptional, typesContainsNullable, Dependency, DependencyManager, NO_TSDATA, WARN_NOT_FOUND_DEPENDENCY_GIR_FILE, WARN_IGNORE_MULTIPLE_CALLBACKS, WARN_IGNORE_MULTIPLE_FUNC_DESC, PackageData, } from '@ts-for-gir/lib' import { TemplateProcessor } from './template-processor.js' import { PackageDataParser } from './package-data-parser.js' import type { GenerateConfig, GirClassElement, GirCallableParamElement, GirSignalElement, GirEnumElement, GirAliasElement, GirInterfaceElement, GirUnionElement, GirModulesGrouped, GirRecordElement, GirBitfieldElement, GirInstanceParameter, GirModule, TsGenericParameter, TsType, TsDoc, TsFunction, TsCallback, TsSignal, TsMember, TsVar, TsProperty, TsParameter, } from '@ts-for-gir/lib' export class TypeDefinitionGenerator implements Generator { protected log: Logger protected dependencyManager: DependencyManager protected packageData?: PackageDataParser /** Override config, used to override the config temporarily to generate both ESM and CJS for NPM packages */ protected overrideConfig: Partial<GenerateConfig> = {} /** Get the current config, including the override config */ protected get config(): GenerateConfig { return { ...this._config, ...this.overrideConfig } } /** * @param _config The config to use without the override config */ constructor(protected readonly _config: GenerateConfig) { this.log = new Logger(this.config.environment, this.config.verbose, TypeDefinitionGenerator.name) this.dependencyManager = DependencyManager.getInstance(this.config) if (this.config.package) { this.packageData = new PackageDataParser(this.config) } } /** * * @param namespace E.g. 'Gtk' * @param packageName E.g. 'Gtk-3.0' * @param asExternType Currently only used for node type imports */ protected generateModuleDependenciesImport(packageName: string): string[] { const def: string[] = [] const dep = this.dependencyManager.get(packageName) // if (this.config.package) { // if (this.config.buildType === 'types') { // // See https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html // def.push(`/// <reference types="${this.config.npmScope}/${dep.importName}" />`) // } // } else { // if (this.config.buildType === 'types') { // // See https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html // def.push(`/// <reference path="${dep.importName}.d.ts" />`) // } // } def.push(dep.importDef) return def } protected generateExport(type: string, name: string, definition: string, indentCount = 0) { const exp = !this.config.noNamespace ? '' : 'export ' const indent = generateIndent(indentCount) if (!definition.startsWith(':')) { definition = ' ' + definition } return `${indent}${exp}${type} ${name}${definition}` } protected generateProperty(tsProp: TsProperty, onlyStatic: boolean, namespace: string, indentCount = 0) { if (!tsProp) { throw new Error('[generateProperty] Not all required properties set!') } const desc: string[] = [] const isStatic = tsProp.isStatic if ((isStatic && !onlyStatic) || (!isStatic && onlyStatic)) { return desc } if (!tsProp.hasUnresolvedConflict) { desc.push(...this.addGirDocComment(tsProp.doc, indentCount)) } const indent = generateIndent(indentCount) const varDesc = this.generateVariable(tsProp, namespace, 0, false) const staticStr = isStatic ? 'static ' : '' const readonly = tsProp.readonly ? 'readonly ' : '' // temporary solution, will be solved differently later const commentOut = tsProp.hasUnresolvedConflict ? '// Has conflict: ' : '' desc.push(`${indent}${commentOut}${staticStr}${readonly}${varDesc}`) return desc } protected generateProperties( tsProps: TsProperty[], onlyStatic: boolean, namespace: string, comment: string, indentCount = 0, ) { const def: string[] = [] for (const tsProp of tsProps) { def.push(...this.generateProperty(tsProp, onlyStatic, namespace, indentCount)) } if (def.length > 0) { def.unshift(...this.addInfoComment(comment, indentCount)) } return def } protected generateVariableCallbackType(tsType: TsType, namespace: string) { // The type of a callback is a functions definition let typeStr = 'any' const { callbacks } = tsType if (!callbacks.length) return typeStr if (callbacks.length > 1) { this.log.warn(WARN_IGNORE_MULTIPLE_CALLBACKS) } const girCallback = callbacks[0] if (!girCallback._tsData) { throw new Error(NO_TSDATA('generateVariableCallbackType')) } const funcDesc = this.generateFunction(girCallback._tsData, false, namespace, 0) if (girCallback._tsData && funcDesc?.length) { if (funcDesc.length > 1) { this.log.warn(WARN_IGNORE_MULTIPLE_FUNC_DESC) } typeStr = funcDesc[0] } return typeStr } protected generateVariable(tsVar: TsProperty | TsVar, namespace: string, indentCount = 0, allowCommentOut = true) { const indent = generateIndent(indentCount) const name = tsVar.name // Constants are not optional const optional = tsVar.tsTypeName !== 'constant' && typesContainsOptional(tsVar.type) if (!name) { throw new Error('[generateVariable] "name" not set!') } const affix = optional ? '?' : '' const typeStr = this.generateTypes(tsVar.type, namespace) // temporary solution, will be solved differently later const commentOut = allowCommentOut && tsVar.hasUnresolvedConflict ? '// Has conflict: ' : '' return `${indent}${commentOut}${name}${affix}: ${typeStr}` } protected generateType(tsType: TsType, namespace: string, generateNullable = true) { let typeName = removeNamespace(tsType.type, namespace) if (tsType.callbacks.length) { typeName = this.generateVariableCallbackType(tsType, namespace) } if (!typeName) { throw new Error('[generateVariable] "typeName" not set!') } let prefix = tsType.isArray ? '[]' : '' if (generateNullable && tsType.nullable) { prefix += ' | null' } // We just use the generic values here const genericStr = this.generateGenericValues(tsType, namespace) return `${typeName}${genericStr}${prefix}` } protected generateTypes(tsTypes: TsType[], namespace: string) { let def = '' for (const tsType of tsTypes) { const separator = tsType.leftSeparator || '|' const typeStr = this.generateType(tsType, namespace, false) if (!def) { def = typeStr } else { def += ` ${separator} ${typeStr}` } } const hasNullable = typesContainsNullable(tsTypes) if (hasNullable) { if (tsTypes.length > 1) { def = `(${def}) | null` } else { def += ' | null' } } return def } protected generateGenericValues(tsType: TsType, namespace: string) { // We just use the generic values here const genericValues = tsType.generics.map((g) => removeNamespace(g.value || '', namespace)).filter((g) => !!g) const genericStr = tsType.generics.length ? `<${genericValues.join(', ')}>` : '' return genericStr } /** * Generates signals from all properties of a base class * TODO: Build new `GirSignalElement`s in `gir-module.ts` instead of generate the strings directly * @param girClass * @param namespace * @param indentCount * @returns */ protected generateClassPropertySignals( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, namespace: string, indentCount = 1, ) { const def: string[] = [] if (!girClass._tsData || !girClass._fullSymName || !girClass._module) { throw new Error(NO_TSDATA('generateClassPropertySignals')) } if (girClass._tsData?.isDerivedFromGObject) { if (girClass._tsData.propertySignalMethods.length > 0) { def.push( ...this.addInfoComment( `Class property signals of ${girClass._module?.packageName}.${girClass._fullSymName}`, indentCount, ), ) for (const tsSignalMethod of girClass._tsData.propertySignalMethods) { if (!tsSignalMethod) { continue } def.push(...this.generateFunction(tsSignalMethod, false, namespace, indentCount)) } } } return def } protected generateInParameters( inParams: GirCallableParamElement[], instanceParameters: GirInstanceParameter[], namespace: string, ) { const inParamsDef: string[] = [] // TODO: Should use of a constructor, and even of an instance, be discouraged? for (const instanceParameter of instanceParameters) { if (instanceParameter._tsData) { let { structFor } = instanceParameter._tsData const { name } = instanceParameter._tsData const gobject = namespace === 'GObject' || namespace === 'GLib' ? '' : 'GObject.' structFor = removeNamespace(structFor, namespace) const returnTypes = [structFor, 'Function', `${gobject}GType`] inParamsDef.push(`${name}: ${returnTypes.join(' | ')}`) } } for (const inParam of inParams) { if (inParam._tsData) inParamsDef.push(...this.generateParameter(inParam._tsData, namespace)) } return inParamsDef } protected generateSignals( girSignals: GirSignalElement[], girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, namespace: string, indentCount = 0, ) { const def: string[] = [] for (const girSignal of girSignals) { if (girSignal._tsData?.tsMethods?.length) { for (const tsSignalMethod of girSignal._tsData?.tsMethods) { if (!tsSignalMethod) { continue } def.push(...this.generateFunction(tsSignalMethod, false, namespace, indentCount)) } } } return def } /** * Adds documentation comments * @see https://github.com/microsoft/tsdoc * @param lines * @param indentCount */ protected addTSDocCommentLines(lines: string[], indentCount = 0): string[] { const def: string[] = [] const indent = generateIndent(indentCount) def.push(`${indent}/**`) for (const line of lines) { def.push(`${indent} * ${line}`) } def.push(`${indent} */`) return def } /** * Adds the documentation as comments * @see https://github.com/microsoft/tsdoc * @param girDoc * @param indentCount * @param overwriteDoc * @returns */ protected addGirDocComment(tsDoc: TsDoc | undefined, indentCount = 0, overwriteDoc?: Partial<TsDoc>) { const desc: string[] = [] const indent = generateIndent(indentCount) if (this.config.noComments) { return desc } const text = overwriteDoc?.text || tsDoc?.text const tags = overwriteDoc?.tags || tsDoc?.tags || [] if (text) { desc.push(`${indent}/**`) if (text) { const lines = text.split('\n') if (lines.length) { for (const line of lines) { desc.push(`${indent} * ${line}`) } } } for (const tag of tags) { if (tag.paramName) { desc.push(`${indent} * @${tag.tagName} ${tag.paramName} ${tag.text}`) } else { desc.push(`${indent} * @${tag.tagName} ${tag.text}`) } } desc.push(`${indent} */`) } return desc } /** * Adds an info comment, is used for debugging the generated types * @param comment * @param indentCount * @returns */ protected addInfoComment(comment?: string, indentCount = 0) { const def: string[] = [] if (this.config.noDebugComments) { return def } const indent = generateIndent(indentCount) if (comment) { def.push('') def.push(`${indent}// ${comment}`) def.push('') } return def } /** * Adds an inline info comment, is used for debugging the generated types * @param comment * @param indentCount * @returns */ protected addInlineInfoComment(comment?: string, indentCount = 0) { const def: string[] = [] if (this.config.noDebugComments) { return def } const indent = generateIndent(indentCount) if (comment) { def.push(`${indent}/* ${comment} */`) } return def } protected mergeDescs(descs: string[], comment?: string, indentCount = 1) { const def: string[] = [] const indent = generateIndent(indentCount) for (const desc of descs) { def.push(`${indent}${desc}`) } if (def.length > 0) { def.unshift(...this.addInfoComment(comment, indentCount)) } return def } protected generateParameter(tsParam: TsParameter, namespace: string) { if (typeof tsParam?.name !== 'string') { throw new Error(NO_TSDATA('generateParameter')) } const types = tsParam.type const name = tsParam.name const typeStr = this.generateTypes(types, namespace) const optional = typesContainsOptional(types) && !tsParam.isRest const affix = optional ? '?' : '' const prefix = tsParam.isRest ? '...' : '' return [`${prefix}${name}${affix}: ${typeStr}`] } /** * * @param tsGenerics * @param isOut If this generic parameters are out do only generate the type parameter names * @returns */ protected generateGenericParameters(tsGenerics?: TsGenericParameter[], isOut = false) { const desc: string[] = [] if (!tsGenerics?.length) { return '' } for (const tsGeneric of tsGenerics) { if (!tsGeneric.name) { continue } let genericStr = `${tsGeneric.name}` if (!isOut && tsGeneric.extends) { genericStr += ` extends ${tsGeneric.extends}` } if (!isOut && tsGeneric.value) { genericStr += ` = ${tsGeneric.value}` } desc.push(genericStr) } return `<${desc.join(', ')}>` } protected generateOutParameterReturn(girParam: GirCallableParamElement, namespace: string) { const desc: string[] = [] if (!girParam._tsData) { this.log.warn(NO_TSDATA('generateOutParameterReturn')) return desc } const { name } = girParam._tsData const typeStr = this.generateTypes(girParam._tsData.type, namespace) desc.push(`/* ${name} */ ${typeStr}`) return desc } protected generateFunctionReturn(tsFunction: TsFunction | TsCallback | TsSignal, namespace: string) { if (tsFunction.name === 'constructor') { return '' } const overrideReturnType = tsFunction.overrideReturnType const outParams = tsFunction.outParams const retTypeIsVoid = tsFunction.retTypeIsVoid const isPromise = tsFunction.isPromise || false const typeStr = this.generateTypes(tsFunction.returnTypes, namespace) let desc = typeStr if (overrideReturnType) { desc = removeNamespace(overrideReturnType, namespace) } // TODO: Transform the outParams to `tsFunction.returnTypes` to move this logic to `gir-module.ts` else if (outParams.length + (retTypeIsVoid ? 0 : 1) > 1) { const outParamsDesc: string[] = [] if (!retTypeIsVoid) { outParamsDesc.push(`/* returnType */ ${typeStr}`) } for (const outParam of outParams) { outParamsDesc.push(...this.generateOutParameterReturn(outParam, namespace)) } desc = outParamsDesc.join(', ') desc = `[ ${desc} ]` } else if (outParams.length === 1 && retTypeIsVoid) { desc = this.generateOutParameterReturn(outParams[0], namespace).join(' ') } if (isPromise) { desc = `globalThis.Promise<${desc}>` } return desc } protected generateFunction( tsFunction: TsFunction | TsCallback | TsSignal | undefined, /** If true only generate static functions otherwise generate only non static functions */ onlyStatic: boolean, namespace: string, indentCount = 1, overloads = true, ) { const def: string[] = [] const indent = generateIndent(indentCount) if (!tsFunction) { this.log.warn(NO_TSDATA('generateFunction')) return def } let { name } = tsFunction const { isStatic } = tsFunction const { isArrowType, isGlobal, inParams, instanceParameters } = tsFunction if ((isStatic && !onlyStatic) || (!isStatic && onlyStatic)) { return def } if (tsFunction.doc && !tsFunction.hasUnresolvedConflict) def.push(...this.addGirDocComment(tsFunction.doc, indentCount)) const staticStr = isStatic && tsFunction.name !== 'constructor' ? 'static ' : '' const globalStr = isGlobal ? 'function ' : '' const genericStr = this.generateGenericParameters(tsFunction.generics) // temporary solution, will be solved differently later const commentOut = tsFunction.hasUnresolvedConflict ? '// Has conflict: ' : '' let exportStr = '' // `tsType === 'function'` are a global methods which can be exported if (isGlobal) { exportStr = !this.config.noNamespace ? '' : 'export ' } const returnType = this.generateFunctionReturn(tsFunction, namespace) let retSep = '' if (returnType) { if (isArrowType) { name = '' retSep = ' =>' } else { retSep = ':' } } const inParamsDef: string[] = this.generateInParameters(inParams, instanceParameters, namespace) def.push( `${indent}${commentOut}${exportStr}${staticStr}${globalStr}${name}${genericStr}(${inParamsDef.join( ', ', )})${retSep} ${returnType}`, ) // Add overloaded methods if (overloads && tsFunction.overloads.length > 0) { def.push(...this.addInfoComment(`Overloads of ${name}`, indentCount)) for (const func of tsFunction.overloads) { def.push(...this.generateFunction(func, onlyStatic, namespace, indentCount, false)) } } return def } protected generateFunctions( tsFunctions: TsFunction[], onlyStatic: boolean, namespace: string, indentCount = 1, comment?: string, ) { const def: string[] = [] for (const girFunction of tsFunctions) { def.push(...this.generateFunction(girFunction, onlyStatic, namespace, indentCount)) } if (def.length > 0) { def.unshift(...this.addInfoComment(comment, indentCount)) } return def } protected generateCallbackInterface( tsCallback: TsCallback | TsSignal, namespace: string, indentCount = 0, classModuleName?: string, ) { const def: string[] = [] if (!tsCallback?.tsCallbackInterface) { this.log.warn(NO_TSDATA('generateCallbackInterface')) return def } def.push(...this.addGirDocComment(tsCallback.doc, indentCount, tsCallback.tsCallbackInterface.overwriteDoc)) const indent = generateIndent(indentCount) const indentBody = generateIndent(indentCount + 1) const { inParams, instanceParameters } = tsCallback const returnTypeStr = this.generateTypes(tsCallback.returnTypes, namespace) // Get name, remove namespace and remove module class name prefix let { name } = tsCallback.tsCallbackInterface const { generics } = tsCallback.tsCallbackInterface name = removeNamespace(name, namespace) if (classModuleName) name = removeClassModule(name, classModuleName) const genericParameters = this.generateGenericParameters(generics) const inParamsDef: string[] = this.generateInParameters(inParams, instanceParameters, namespace) const interfaceHead = `${name}${genericParameters}` def.push(this.generateExport('interface', `${interfaceHead}`, '{', indentCount)) def.push(`${indentBody}(${inParamsDef.join(', ')}): ${returnTypeStr}`) def.push(indent + '}') return def } protected generateCallbackInterfaces( tsCallbacks: Array<TsCallback | TsSignal>, namespace: string, indentCount = 0, classModuleName: string, comment?: string, ) { const def: string[] = [] for (const tsCallback of tsCallbacks) { def.push(...this.generateCallbackInterface(tsCallback, namespace, indentCount, classModuleName), '') } if (def.length > 0) { def.unshift(...this.addInfoComment(comment, indentCount)) } return def } protected generateEnumeration(girEnum: GirEnumElement | GirBitfieldElement, indentCount = 0) { const desc: string[] = [] if (!girElementIsIntrospectable(girEnum)) { return desc } if (!girEnum._tsData) { this.log.warn(NO_TSDATA('generateEnumeration')) return desc } desc.push(...this.addGirDocComment(girEnum._tsData.doc, indentCount)) const { name } = girEnum._tsData desc.push(this.generateExport('enum', name, '{', indentCount)) if (girEnum.member) { for (const girEnumMember of girEnum.member) { if (!girEnumMember._tsData) continue desc.push(...this.generateEnumerationMember(girEnumMember._tsData, indentCount + 1)) } } desc.push('}') return desc } protected generateEnumerationMember(tsMember: TsMember, indentCount = 1) { const desc: string[] = [] if (!tsMember) { this.log.warn(NO_TSDATA('generateEnumerationMember')) return desc } desc.push(...this.addGirDocComment(tsMember.doc, indentCount)) const indent = generateIndent(indentCount) desc.push(`${indent}${tsMember.name},`) return desc } protected generateConstant(tsConst: TsVar, namespace: string, indentCount = 0) { const desc: string[] = [] if (!tsConst.hasUnresolvedConflict) { desc.push(...this.addGirDocComment(tsConst.doc, indentCount)) } const indent = generateIndent(indentCount) const exp = !this.config.noNamespace ? '' : 'export ' const varDesc = this.generateVariable(tsConst, namespace, 0) desc.push(`${indent}${exp}const ${varDesc}`) return desc } protected generateAlias(girAlias: GirAliasElement, namespace: string, indentCount = 0) { const desc: string[] = [] if (!girElementIsIntrospectable(girAlias)) { return '' } if (!girAlias._tsData) { this.log.warn(NO_TSDATA('generateAlias')) return desc } const indent = generateIndent(indentCount) const exp = !this.config.noNamespace ? '' : 'export ' const type = removeNamespace(girAlias._tsData.type, namespace) desc.push(`${indent}${exp}type ${girAlias._tsData.name} = ${type}`) return desc } protected generateConstructPropsInterface( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, namespace: string, indentCount = 0, ) { const def: string[] = [] if (!girClass._tsData || !girClass._fullSymName || !girClass._module) { throw new Error(NO_TSDATA('generateConstructPropsInterface')) } if (!girClass._tsData.isDerivedFromGObject) { return def } const indent = generateIndent(indentCount) const exp = !this.config.noNamespace ? '' : 'export ' let ext = '' if (girClass._tsData.inheritConstructPropInterfaceNames.length) { const constructPropInterfaceNames = girClass._tsData.inheritConstructPropInterfaceNames.map((n) => removeNamespace(n, namespace), ) ext = `extends ${constructPropInterfaceNames.join(', ')} ` } // Remove namespace and class module name const constructPropInterfaceName = removeClassModule( removeNamespace(girClass._tsData.constructPropInterfaceName, namespace), girClass._tsData.name, ) def.push(...this.addInfoComment('Constructor properties interface', indentCount)) // START BODY { def.push(`${indent}${exp}interface ${constructPropInterfaceName} ${ext}{`) def.push( ...this.generateProperties( girClass._tsData.constructProps.map((cp) => cp._tsData).filter((cp) => !!cp) as TsProperty[], false, namespace, `Own constructor properties of ${girClass._module.packageName}.${girClass._fullSymName}`, indentCount + 1, ), ) def.push(`${indent}}`, '') } // END BODY return def } protected generateClassFields( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, onlyStatic: boolean, namespace: string, indentCount = 1, ) { const def: string[] = [] if (!girClass._tsData || !girClass._fullSymName || !girClass._module) { throw new Error(NO_TSDATA('generateClassFields')) } def.push( ...this.generateProperties( girClass._tsData.fields.map((f) => f._tsData).filter((f) => !!f) as TsProperty[], onlyStatic, namespace, `Own fields of ${girClass._module.packageName}.${girClass._fullSymName}`, indentCount, ), ) return def } protected generateClassProperties( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, onlyStatic: boolean, namespace: string, indentCount = 1, ) { const def: string[] = [] if (!girClass._tsData || !girClass._fullSymName || !girClass._module) { throw new Error(NO_TSDATA('generateClassProperties')) } def.push( ...this.generateProperties( girClass._tsData.properties.map((p) => p._tsData).filter((p) => !!p) as TsProperty[], onlyStatic, namespace, `Own properties of ${girClass._module.packageName}.${girClass._fullSymName}`, indentCount, ), ) def.push( ...this.generateProperties( girClass._tsData.conflictProperties, onlyStatic, namespace, `Conflicting properties`, indentCount, ), ) return def } protected generateClassMethods( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, onlyStatic: boolean, namespace: string, indentCount = 1, ) { const def: string[] = [] if (!girClass._tsData || !girClass._fullSymName || !girClass._module) { throw new Error(NO_TSDATA('generateClassMethods')) } def.push( ...this.generateFunctions( girClass._tsData.methods.map((girFunc) => girFunc._tsData).filter((tsFunc) => !!tsFunc) as TsFunction[], onlyStatic, namespace, indentCount, `Owm ${onlyStatic ? 'static ' : ''}methods of ${girClass._module.packageName}.${girClass._fullSymName}`, ), ) def.push( ...this.generateFunctions( girClass._tsData.conflictMethods, onlyStatic, namespace, indentCount, `Conflicting ${onlyStatic ? 'static ' : ''}methods`, ), ) return def } protected generateClassConstructors( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, namespace: string, indentCount = 1, ) { const def: string[] = [] if (!girClass._tsData || !girClass._fullSymName || !girClass._module) { throw new Error(NO_TSDATA('generateClassConstructors')) } // Constructors def.push( ...this.generateFunctions( girClass._tsData.constructors .map((girFunc) => girFunc._tsData) .filter((tsFunc) => !!tsFunc) as TsFunction[], true, namespace, indentCount, ), ) // _init method def.push( ...this.generateFunctions( girClass._tsData.constructors .map((girFunc) => girFunc._tsData) .filter((tsFunc) => !!tsFunc) as TsFunction[], false, namespace, indentCount, ), ) // Pseudo constructors def.push( ...this.generateFunctions( girClass._tsData.staticFunctions .map((girFunc) => girFunc._tsData) .filter((tsFunc) => !!tsFunc) as TsFunction[], true, namespace, indentCount, ), ) if (def.length) { def.unshift( ...this.addInfoComment( `Constructors of ${girClass._module.packageName}.${girClass._fullSymName}`, indentCount, ), ) } return def } /** * Instance methods, vfunc_ prefix * @param girClass */ protected generateClassVirtualMethods( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, namespace: string, indentCount = 1, ) { const def: string[] = [] if (!girClass._tsData || !girClass._fullSymName || !girClass._module) { throw new Error(NO_TSDATA('generateClassVirtualMethods')) } def.push( ...this.generateFunctions( girClass._tsData.virtualMethods .map((girFunc) => girFunc._tsData) .filter((tsFunc) => !!tsFunc) as TsFunction[], false, namespace, indentCount, `Own virtual methods of ${girClass._module.packageName}.${girClass._fullSymName}`, ), ) return def } protected generateClassSignalInterfaces( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, namespace: string, indentCount = 0, ) { const def: string[] = [] if (!girClass._tsData) { throw new Error(NO_TSDATA('generateClassSignalInterface')) } const tsSignals = girClass._tsData.signals .map((signal) => signal._tsData) .filter((signal) => !!signal) as TsSignal[] def.push( ...this.generateCallbackInterfaces( tsSignals, namespace, indentCount, girClass._tsData.name, 'Signal callback interfaces', ), ) return def } protected generateClassSignals( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, namespace: string, ) { const def: string[] = [] if (!girClass._tsData || !girClass._fullSymName || !girClass._module) { throw new Error(NO_TSDATA('generateClassSignals')) } const signalDescs = this.generateSignals(girClass._tsData.signals, girClass, namespace, 0) def.push( ...this.mergeDescs( signalDescs, `Own signals of ${girClass._module.packageName}.${girClass._fullSymName}`, 1, ), ) return def } protected generateClassModules( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, namespace: string, indentCount = 0, ) { const def: string[] = [] const bodyDef: string[] = [] if (!girClass._tsData) return def const indent = generateIndent(indentCount) const exp = !this.config.noNamespace ? '' : 'export ' // Signal interfaces bodyDef.push(...this.generateClassSignalInterfaces(girClass, namespace, indentCount + 1)) // Properties interface for construction bodyDef.push(...this.generateConstructPropsInterface(girClass, namespace, indentCount + 1)) if (!bodyDef.length) { return [] } // START BODY { def.push(`${indent}${exp}module ${girClass._tsData.name} {`) // Properties interface for construction def.push(...bodyDef) def.push(`${indent}}`, '') } // END BODY return def } /** * In Typescript, interfaces and classes can have the same name, * so we use this to generate interfaces with the same name to implement multiple inheritance * @param girClass * @param namespace */ protected generateImplementationInterface( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, namespace: string, ) { const def: string[] = [] if (!girClass._tsData) return def const genericParameters = this.generateGenericParameters(girClass._tsData.generics) const implementationNames = girClass._tsData.parents .filter((implementation) => implementation.type !== 'parent') .map((implementation) => implementation.localParentName) const ext = implementationNames.length ? ` extends ${implementationNames.join(', ')}` : '' const interfaceHead = `${girClass._tsData.name}${genericParameters}${ext}` // START INTERFACE { def.push(this.generateExport('interface', interfaceHead, '{')) // START BODY { // Properties def.push(...this.generateClassProperties(girClass, false, namespace)) // Fields def.push(...this.generateClassFields(girClass, false, namespace)) // Methods def.push(...this.generateClassMethods(girClass, false, namespace)) // Virtual methods def.push(...this.generateClassVirtualMethods(girClass, namespace)) // Signals def.push(...this.generateClassSignals(girClass, namespace)) // TODO: Generate `GirSignalElement`s instead of generate the signal definition strings directly def.push(...this.generateClassPropertySignals(girClass, namespace)) } // END BODY // END INTERFACE def.push('}') def.push('') } return def } /** * Represents a record, GObject class or interface as a Typescript class * @param girClass * @param namespace */ protected generateClass( girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, namespace: string, ) { const def: string[] = [] if (!girClass._tsData) return def def.push(...this.generateClassModules(girClass, namespace)) def.push(...this.generateImplementationInterface(girClass, namespace)) def.push(...this.addGirDocComment(girClass._tsData.doc, 0)) const genericParameters = this.generateGenericParameters(girClass._tsData.generics) const parentName = girClass._tsData.parents.find((parent) => parent.type === 'parent')?.localParentName const ext = parentName ? ` extends ${parentName}` : '' const classHead = `${girClass._tsData.name}${genericParameters}${ext}` // START CLASS { if (girClass._tsData.isAbstract) { def.push(this.generateExport('abstract class', classHead, '{')) } else { def.push(this.generateExport('class', classHead, '{')) } // START BODY { // Static Properties def.push(...this.generateClassProperties(girClass, true, namespace)) // Static Fields def.push(...this.generateClassFields(girClass, true, namespace)) // Constructors def.push(...this.generateClassConstructors(girClass, namespace)) // Static Methods def.push(...this.generateClassMethods(girClass, true, namespace)) } // END BODY // END CLASS def.push('}') def.push('') } return def } protected async exportModuleJS(moduleTemplateProcessor: TemplateProcessor, girModule: GirModule): Promise<void> { const template = 'module.js' let target = `${girModule.importName}.js` if (this.overrideConfig.moduleType) { if (this.overrideConfig.moduleType === 'cjs') { target = `${girModule.importName}.cjs` } else { target = `${girModule.importName}.mjs` } } if (this.config.outdir) { await moduleTemplateProcessor.create( template, this.config.outdir, target, undefined, undefined, undefined, this.config, ) } else { const { append, prepend } = await moduleTemplateProcessor.load(template, {}, this.config) this.log.log(append + prepend) } } protected async exportModuleAmbientTS( moduleTemplateProcessor: TemplateProcessor, girModule: GirModule, ): Promise<void> { const template = 'module-ambient.d.ts' let target = `${girModule.importName}-ambient.d.ts` if (this.overrideConfig.moduleType) { if (this.overrideConfig.moduleType === 'cjs') { target = `${girModule.importName}-ambient.d.cts` } else { target = `${girModule.importName}-ambient.d.mts` } } if (this.config.outdir) { await moduleTemplateProcessor.create( template, this.config.outdir, target, undefined, undefined, undefined, this.config, ) } else { const { append, prepend } = await moduleTemplateProcessor.load(template, {}, this.config) this.log.log(append + prepend) } } protected async exportModuleImportTS( moduleTemplateProcessor: TemplateProcessor, girModule: GirModule, ): Promise<void> { const template = 'module-import.d.ts' let target = `${girModule.importName}-import.d.ts` if (this.overrideConfig.moduleType) { if (this.overrideConfig.moduleType === 'cjs') { target = `${girModule.importName}-import.d.cts` } else { target = `${girModule.importName}-import.d.mts` } } if (this.config.outdir) { await moduleTemplateProcessor.create( template, this.config.outdir, target, undefined, undefined, undefined, this.config, ) } else { const { append, prepend } = await moduleTemplateProcessor.load(template, {}, this.config) this.log.log(append + prepend) } } protected async exportModuleTS(moduleTemplateProcessor: TemplateProcessor, girModule: GirModule): Promise<void> { const template = 'module.d.ts' let explicitTemplate = `${girModule.importName}.d.ts` let target = `${girModule.importName}.d.ts` if (this.overrideConfig.moduleType) { if (this.overrideConfig.moduleType === 'cjs') { target = `${girModule.importName}.d.cts` } else { target = `${girModule.importName}.d.mts` } } // Remove `node-` prefix for node environment if (this.config.environment === 'node' && explicitTemplate.startsWith('node-')) { explicitTemplate = explicitTemplate.substring(5) } const out: string[] = [] out.push(...this.addTSDocCommentLines([girModule.packageName])) out.push('') // if (this.config.environment === 'gjs') { // out.push(...this.generateModuleDependenciesImport('Gjs') // } // Module dependencies as type references or imports // TODO: Move to template for (const dependency of girModule.transitiveDependencies) { // Don't reference yourself as a dependency if (girModule.packageName !== dependency.packageName) { if (dependency.exists) { out.push(...this.generateModuleDependenciesImport(dependency.packageName)) } else { out.push(`// WARN: Dependency not found: '${dependency.packageName}'`) this.log.warn(WARN_NOT_FOUND_DEPENDENCY_GIR_FILE(dependency.filename)) } } } // START Namespace { if (!this.config.noNamespace) { out.push('') out.push(`export namespace ${girModule.namespace} {`) } // Newline out.push('') if (girModule.ns.enumeration) for (const girEnum of girModule.ns.enumeration) out.push(...this.generateEnumeration(girEnum)) if (girModule.ns.bitfield) for (const girBitfield of girModule.ns.bitfield) out.push(...this.generateEnumeration(girBitfield)) if (girModule.ns.constant) for (const girConst of girModule.ns.constant) { if (!girConst._tsData) continue out.push(...this.generateConstant(girConst._tsData, girModule.namespace, 0)) } if (girModule.ns.function) for (const girFunc of girModule.ns.function) { if (!girFunc._tsData) { // this.log.warn(NO_TSDATA('exportModuleTS functions')) continue } out.push(...this.generateFunction(girFunc._tsData, false, girModule.namespace, 0)) } if (girModule.ns.callback) for (const girCallback of girModule.ns.callback) { if (!girCallback._tsData) continue out.push(...this.generateCallbackInterface(girCallback._tsData, girModule.namespace)) } if (girModule.ns.interface) for (const girIface of girModule.ns.interface) if (girIface._module) { out.push(...this.generateClass(girIface, girIface._module.namespace)) } // Extra interfaces if a template with the module name (e.g. '../templates/gobject-2-0.d.ts') is found // E.g. used for GObject-2.0 to help define GObject classes in js; // these aren't part of gi. if (moduleTemplateProcessor.exists(explicitTemplate)) { const { append, prepend } = await moduleTemplateProcessor.load(explicitTemplate, {}, this.config) // TODO push prepend and append to the right position out.push(append + prepend) } if (girModule.ns.class) for (const gitClass of girModule.ns.class) if (gitClass._module) { out.push(...this.generateClass(gitClass, gitClass._module.namespace)) } if (girModule.ns.record) for (const girRecord of girModule.ns.record) if (girRecord._module) { out.push(...this.generateClass(girRecord, girRecord._module.namespace)) } if (girModule.ns.union) for (const girUnion of girModule.ns.union) if (girUnion._module) { out.push(...this.generateClass(girUnion, girUnion._module.namespace)) } if (girModule.ns.alias) // GType is not a number in GJS for (const girAlias of girModule.ns.alias) if (girModule.packageName !== 'GObject-2.0' || girAlias.$.name !== 'Type') out.push(...this.generateAlias(girAlias, girModule.namespace, 1)) if (this.config.environment === 'gjs') { // Properties added to every GIRepositoryNamespace // https://gitlab.gnome.org/GNOME/gjs/-/blob/master/gi/ns.cpp#L186-190 out.push( ...this.generateConstant( { doc: { text: 'Name of the imported GIR library', tags: [ { text: 'https://gitlab.gnome.org/GNOME/gjs/-/blob/master/gi/ns.cpp#L188', tagName: 'see', paramName: '', }, ], }, name: '__name__', type: [ { type: 'string', optional: false, nullable: false, callbacks: [], generics: [], isArray: false, isFunction: false, isCallback: false, }, ], isInjected: false, tsTypeName: 'constant', girTypeName: 'constant', }, girModule.namespace, 0, ), ...this.generateConstant( { doc: { text: 'Version of the imported GIR library', tags: [ { text: 'https://gitlab.gnome.org/GNOME/gjs/-/blob/master/gi/ns.cpp#L189', tagName: 'see', paramName: '', }, ], }, name: '__version__', type: [ { type: 'string',