UNPKG

nihilqui

Version:

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

1,459 lines (1,263 loc) 107 kB
// TODO move this class into a web-worker? https://www.npmjs.com/package/web-worker import { Transformation, FULL_TYPE_MAP, PRIMITIVE_TYPE_MAP, ARRAY_TYPE_MAP, IGNORE_GIR_TYPE_TS_DOC_TYPES, } from './transformation.js' import { STATIC_NAME_ALREADY_EXISTS, MAX_CLASS_PARENT_DEPTH } from './constants.js' import { Logger } from './logger.js' import { Injector } from './injection/injector.js' import { GirFactory } from './gir-factory.js' import { ConflictResolver } from './conflict-resolver.js' import { DependencyManager } from './dependency-manager.js' import { NO_TSDATA, WARN_NOT_FOUND_TYPE, WARN_CONSTANT_ALREADY_EXPORTED, WARN_DUPLICATE_SYMBOL, WARN_DUPLICATE_PARAMETER, WARN_DUPLICATE_ENUM_IDENTIFIER, } from './messages.js' import { isEqual, find, clone, girBool, removeNamespace, addNamespace, girElementIsIntrospectable } from './utils.js' import { SymTable } from './symtable.js' import { LibraryVersion } from './library-version.js' import { GirDirection } from './types/index.js' import type { Dependency, GirRepository, GirNamespace, GirAliasElement, GirEnumElement, GirMemberElement, GirFunctionElement, GirClassElement, GirArrayType, GirType, GirCallableParams, GirCallableParamElement, GirVirtualMethodElement, GirSignalElement, GirCallableReturn, GirRecordElement, GirCallbackElement, GirConstantElement, GirBitfieldElement, GirFieldElement, GirMethodElement, GirPropertyElement, GirAnyElement, GirUnionElement, GirInstanceParameter, GirInterfaceElement, GirConstructorElement, GirDocElement, TypeGirVariable, TypeGirClass, TypeGirEnumerationMember, LocalNameCheck, LocalNameType, LocalName, LocalNames, TsDoc, TsDocTag, TsClass, TsMethod, TsFunction, TsProperty, TsVar, TsParameter, TsInstanceParameter, TsCallbackInterface, TsMember, TsEnum, TsAlias, TsType, TsGenericParameter, TsCallback, InheritanceTable, ParsedGir, GenerateConfig, ClassParent, InjectionParameter, TsSignal, PromisifyFunc, } from './types/index.js' export class GirModule { /** * Array of all gir modules */ static allGirModules: GirModule[] = [] /** * E.g. 'Gtk' */ namespace: string /** * E.g. '4.0' */ version = '0.0' /** * E.g. 'Gtk-4.0' */ packageName: string /** * E.g. 'Gtk40' * Is used in the generated index.d.ts, for example: `import * as Gtk40 from "./Gtk-4.0.js";` */ importNamespace: string importName: string /** * The version of the library as an object. * E.g. `{ major: 4, minor: 0, patch: 0 }` or as string `4.0.0`' */ libraryVersion: LibraryVersion dependencies: Dependency[] = [] private _transitiveDependencies: Dependency[] = [] set transitiveDependencies(deps: Dependency[]) { this._transitiveDependencies = this.checkTransitiveDependencies(deps) } get transitiveDependencies(): Dependency[] { return this._transitiveDependencies } get allDependencies(): Dependency[] { return [...new Set([...this.dependencies, ...this.transitiveDependencies])] } repo: GirRepository ns: GirNamespace = { $: { name: '', version: '0.0' } } /** * Used to find namespaces that are used in other modules */ symTable: SymTable transformation: Transformation girFactory = new GirFactory() dependencyManager: DependencyManager conflictResolver: ConflictResolver log: Logger inject: Injector extends?: string /** * To prevent constants from being exported twice, the names already exported are saved here for comparison. * Please note: Such a case is only known for Zeitgeist-2.0 with the constant "ATTACHMENT" */ constNames: { [varName: string]: GirConstantElement } = {} constructor(xml: ParsedGir, private readonly config: GenerateConfig) { this.repo = xml.repository if (!this.repo.namespace || !this.repo.namespace.length) { throw new Error(`Namespace not found!`) } this.dependencyManager = DependencyManager.getInstance(this.config) this.dependencies = this.dependencyManager.fromGirIncludes(this.repo.include || []) this.ns = this.repo.namespace[0] this.namespace = this.ns.$.name this.version = this.ns.$.version this.packageName = `${this.namespace}-${this.version}` this.libraryVersion = new LibraryVersion(this.ns.constant, this.version) this.transformation = new Transformation(config) this.log = new Logger(config.environment, config.verbose, this.packageName || 'GirModule') this.conflictResolver = new ConflictResolver(config.environment, config.verbose) this.inject = new Injector(this.config.environment) this.importNamespace = this.transformation.transformModuleNamespaceName(this.packageName) this.importName = this.transformation.transformImportName(this.packageName) this.symTable = new SymTable(this.config, this.packageName, this.namespace) } private checkTransitiveDependencies(transitiveDependencies: Dependency[]) { // Always pull in GObject-2.0, as we may need it for e.g. GObject-2.0.type if (this.packageName !== 'GObject-2.0') { if (!find(transitiveDependencies, (x) => x.packageName === 'GObject-2.0')) { transitiveDependencies.push(this.dependencyManager.get('GObject', '2.0')) } } // Add missing dependencies if (this.packageName === 'UnityExtras-7.0') { if (!find(transitiveDependencies, (x) => x.packageName === 'Unity-7.0')) { transitiveDependencies.push(this.dependencyManager.get('Unity', '7.0')) } } if (this.packageName === 'UnityExtras-6.0') { if (!find(transitiveDependencies, (x) => x.packageName === 'Unity-6.0')) { transitiveDependencies.push(this.dependencyManager.get('Unity', '6.0')) } } if (this.packageName === 'GTop-2.0') { if (!find(transitiveDependencies, (x) => x.packageName === 'GLib-2.0')) { transitiveDependencies.push(this.dependencyManager.get('GLib', '2.0')) } } return transitiveDependencies } private getTsDoc(girDoc: GirDocElement) { const tsDoc: TsDoc = { text: '', tags: [], } if (girDoc.doc?.[0]?._) { let text = girDoc.doc?.[0]?._ || '' text = this.transformation.transformGirDocText(text) tsDoc.text = text } return tsDoc } private getTsDocGirElementTags(tsTypeName?: string, girTypeName?: string): TsDocTag[] { const tags: TsDocTag[] = [] if (!girTypeName || IGNORE_GIR_TYPE_TS_DOC_TYPES.includes(girTypeName)) { return tags } tags.push({ tagName: girTypeName, paramName: '', text: '', }) return tags } private getTsDocReturnTags( girElement?: | GirCallbackElement | GirConstructorElement | GirFunctionElement | GirMethodElement | GirSignalElement | GirVirtualMethodElement, ): TsDocTag[] { const girReturnValue = girElement?.['return-value']?.[0] if (!girReturnValue || !girReturnValue.doc?.[0]?._) { return [] } const returnTag: TsDocTag = { tagName: 'returns', paramName: '', text: this.transformation.transformGirDocTagText(girReturnValue.doc[0]._), } return [returnTag] } private getTsDocInParamTags(inParams?: GirCallableParamElement[]): TsDocTag[] { const tags: TsDocTag[] = [] if (!inParams?.length) { return tags } for (const inParam of inParams) { if (!inParam._tsData) { throw new Error(NO_TSDATA('getTsDocInParamTags')) } if (!inParam._tsData?.doc) { inParam._tsData.doc = this.getTsDoc(inParam) } if (inParam._tsData?.name) { tags.push({ paramName: inParam._tsData.name, tagName: 'param', text: inParam._tsData.doc.text ? this.transformation.transformGirDocTagText(inParam._tsData.doc.text) : '', }) } } return tags } private annotateFunctionArguments( girFunc: | GirMethodElement | GirFunctionElement | GirConstructorElement | GirVirtualMethodElement | GirCallbackElement | GirSignalElement, ): void { const funcName = girFunc._fullSymName if (funcName && girFunc.parameters) { for (const girParams of girFunc.parameters) { if (girParams.parameter) { for (const girParam of girParams.parameter) { girParam._module = this if (girParam.$ && girParam.$.name) { girParam._fullSymName = `${funcName}.${girParam.$.name}` } } } } } } private annotateFunctionReturn( girFunc: | GirMethodElement | GirFunctionElement | GirConstructorElement | GirVirtualMethodElement | GirCallbackElement | GirSignalElement, ): void { const retVals = girFunc['return-value'] if (retVals && girFunc._fullSymName) for (const retVal of retVals) { retVal._module = this retVal.girTypeName = 'callable-return' if (retVal.$ && retVal.$.name) { retVal._fullSymName = `${girFunc._fullSymName}.${retVal.$.name}` } } } /** * Functions which are not part of a class * @param girFuncs */ private annotateFunctions(girFuncs: GirFunctionElement[] | GirCallbackElement[]): void { if (Array.isArray(girFuncs)) for (const girFunc of girFuncs) { if (girFunc?.$?.name) { // girFunc._girType = girType girFunc._fullSymName = `${this.namespace}.${girFunc.$.name}` this.annotateFunctionArguments(girFunc) this.annotateFunctionReturn(girFunc) } } } /** * Functions and methods of a class */ private annotateMethods( girClass: GirClassElement | GirRecordElement | GirInterfaceElement, girFuncs: | GirMethodElement[] | GirFunctionElement[] | GirConstructorElement[] | GirVirtualMethodElement[] | GirSignalElement[], ): void { if (Array.isArray(girFuncs)) for (const girFunc of girFuncs) { if (girFunc?.$?.name) { // girFunc._girType = girType // girFunc._tsType = tsType girFunc._class = girClass const nsName = girClass ? girClass._fullSymName : this.namespace if (nsName) girFunc._fullSymName = `${nsName}.${girFunc.$.name}` this.annotateFunctionArguments(girFunc) this.annotateFunctionReturn(girFunc) } } } /** * Variables which are not part of a class */ private annotateVariables(girVars: GirConstantElement[]): void { for (const girVar of girVars) { girVar._module = this if (girVar.$ && girVar.$.name) { girVar._fullSymName = `${this.namespace}.${girVar.$.name}` } } } private annotateFields( girClass: GirClassElement | GirRecordElement | GirInterfaceElement | null, girVars: GirPropertyElement[], ): void private annotateFields( girClass: GirClassElement | GirRecordElement | GirInterfaceElement | null, girVars: GirFieldElement[], ): void /** * Fields are variables which are part of a class * @see https://www.typescriptlang.org/docs/handbook/2/classes.html#fields */ private annotateFields( girClass: GirClassElement | GirRecordElement | GirInterfaceElement, girVars: GirPropertyElement[] | GirFieldElement[], ): void { for (const girVar of girVars) { const nsName = girClass ? girClass._fullSymName : this.namespace girVar._module = this if (girClass) { girVar._class = girClass } if (girVar.$ && girVar.$.name && nsName) { girVar._fullSymName = `${nsName}.${girVar.$.name}` } } } private annotateClass(girClass: GirClassElement, girTypeName: 'class'): void private annotateClass(girClass: GirRecordElement, girTypeName: 'record'): void private annotateClass(girClass: GirInterfaceElement, girTypeName: 'interface'): void private annotateClass(girClass: GirClassElement | GirRecordElement | GirInterfaceElement) { girClass._module = this girClass._fullSymName = `${this.namespace}.${girClass.$.name}` const constructors = Array.isArray(girClass.constructor) ? girClass.constructor : [] const signals = ((girClass as GirClassElement | GirInterfaceElement).signal || girClass['glib:signal'] || []) as GirSignalElement[] const functions = girClass.function || [] const methods = girClass.method || [] const vMethods = (girClass as GirClassElement)['virtual-method'] || new Array<GirVirtualMethodElement>() const properties = girClass.property const fields = girClass.field this.annotateMethods(girClass, constructors) this.annotateMethods(girClass, functions) this.annotateMethods(girClass, methods) this.annotateMethods(girClass, vMethods) this.annotateMethods(girClass, signals) if (properties) this.annotateFields(girClass, properties) if (fields) this.annotateFields(girClass, fields) } /** * Annotates the custom `_module`, `_fullSymName` and `_girType` properties. * Also registers the element on the `symTable`. * @param girElements * @param girType */ private annotateAndRegisterGirElement(girElements: GirAnyElement[]): void { for (const girElement of girElements) { if (girElement?.$ && girElement.$.name) { // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument if (!girElementIsIntrospectable(girElement as any)) continue girElement._module = this girElement._fullSymName = `${this.namespace}.${girElement.$.name}` if (this.symTable.get(this.allDependencies, girElement._fullSymName)) { this.log.warn(WARN_DUPLICATE_SYMBOL(girElement._fullSymName)) } this.symTable.set(this.allDependencies, girElement._fullSymName, girElement) } } } /** * TODO: find better name for this method * @param girVar * @param fullTypeName * @returns e.g. * ```ts * { * namespace: "Gtk", * resValue: "Gtk.Widget" * } * */ private fullTypeLookup( girVar: | GirCallableParamElement | GirCallableReturn | GirFieldElement | GirAliasElement | GirFieldElement | GirPropertyElement | GirConstantElement, fullTypeName: string | null, ) { let resValue = '' let namespace = '' if (!fullTypeName) { return { value: resValue, fullTypeName, namespace, } } // Fully qualify our type name if (!fullTypeName.includes('.')) { let tryFullTypeName = '' if (!resValue && girVar._module && girVar._module !== this) { tryFullTypeName = `${girVar._module.namespace}.${fullTypeName}` resValue = this.fullTypeLookupWithNamespace(tryFullTypeName).value if (resValue) { fullTypeName = tryFullTypeName namespace = girVar._module.namespace } } if (!resValue) { tryFullTypeName = `${this.namespace}.${fullTypeName}` resValue = this.fullTypeLookupWithNamespace(tryFullTypeName).value if (resValue) { fullTypeName = tryFullTypeName namespace = this.namespace } } const girClass = ( girVar as | GirCallableParamElement | GirCallableReturn | GirFieldElement | GirAliasElement | GirFieldElement | GirPropertyElement )._class as GirClassElement | undefined if (!resValue && girClass?._module?.namespace && girClass._module !== this) { tryFullTypeName = `${girClass._module.namespace}.${fullTypeName}` resValue = this.fullTypeLookupWithNamespace(tryFullTypeName).value if (resValue) { fullTypeName = tryFullTypeName namespace = girClass?._module?.namespace } } } if (!resValue && fullTypeName) { resValue = this.fullTypeLookupWithNamespace(fullTypeName).value } return { value: resValue, namespace, } } /** * this method needs to be refactored, an array can also be an array of an array for example * @param girVar * @returns */ getArrayData( girVar: | GirCallableReturn | GirAliasElement | GirFieldElement | GirCallableParamElement | GirPropertyElement | GirConstantElement, ) { let arrayType: GirType | null = null let arrCType: string | undefined let isArray = false let overrideTypeName: string | undefined let typeArray: GirType[] | undefined let collection: GirArrayType[] | GirType[] | undefined if ((girVar as GirCallableReturn | GirFieldElement).array) { collection = (girVar as GirCallableReturn | GirFieldElement).array } else if (/^GLib.S?List$/.test(girVar.type?.[0].$?.name || '')) { // This converts GLib.List<T> / GLib.SList<T> to T[] collection = girVar.type } if (collection && collection.length > 0) { isArray = true typeArray = collection[0].type if (collection[0].$) { const ea = collection[0].$ arrCType = ea['c:type'] } } if (typeArray && typeArray?.length > 0) { arrayType = typeArray[0] } if (isArray && arrayType?.$?.name && ARRAY_TYPE_MAP[arrayType.$.name]) { isArray = false overrideTypeName = ARRAY_TYPE_MAP[arrayType.$.name] as string | undefined } return { arrCType, arrayType, isArray, overrideTypeName, } } /** * Get the typescript type of a GirElement like a `GirPropertyElement` or `GirCallableReturn` * @param girVar */ private getTsType( girVar: | GirCallableReturn | GirAliasElement | GirFieldElement | GirCallableParamElement | GirPropertyElement | GirConstantElement, tsClass: TsClass | null, defaults: Partial<TsType> = {}, ) { let type: GirType | undefined = girVar.type?.[0] let fullTypeName: string | null = null let typeName = defaults.type || '' let isFunction = defaults.isFunction || false let isCallback = defaults.isCallback || false const nullable = this.typeIsNullable(girVar) || defaults.nullable || false const optional = this.typeIsOptional(girVar) || defaults.optional || false const girCallbacks: GirCallbackElement[] = [] const array = this.getArrayData(girVar) if (array.overrideTypeName) { typeName = array.overrideTypeName } if (array.arrayType) { type = array.arrayType } const cType = type?.$ ? type.$['c:type'] : array.arrCType fullTypeName = type?.$?.name || null const callbacks = (girVar as GirFieldElement).callback if (!typeName && callbacks?.length) { for (const girCallback of callbacks) { if (!girElementIsIntrospectable(girCallback)) continue if (!girCallback._tsData) { const tsCallback = this.getFunctionTsData(girCallback, 'callback', tsClass, { isStatic: false, isArrowType: true, isGlobal: false, isVirtual: false, returnType: null, generics: [], }) if (!tsCallback) continue girCallback._tsData = { ...tsCallback, girTypeName: 'callback', tsTypeName: this.girFactory.girTypeNameToTsTypeName('callback', false), tsCallbackInterface: this.getCallbackInterfaceTsData(girCallback), doc: this.getTsDoc(girCallback), overloads: [], } } if (girCallback._tsData) { girCallbacks.push(girCallback) isFunction = true isCallback = true } } } if (!isFunction) { const res = this.fullTypeLookup(girVar, fullTypeName) if (res.value) { typeName = res.value fullTypeName = typeName } } if (!typeName && type?.$?.name && PRIMITIVE_TYPE_MAP[type.$.name]) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment typeName = PRIMITIVE_TYPE_MAP[type.$.name] } if (cType) { const parsedCType = PRIMITIVE_TYPE_MAP[cType] as string | undefined if (!typeName && parsedCType) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment typeName = parsedCType } } if (!typeName) { typeName = 'any' const logName = cType || fullTypeName || girVar.$.name || '' this.log.warn(WARN_NOT_FOUND_TYPE(logName)) } // TODO: transform array to type with generics? const tsType = this.girFactory.newTsType({ ...defaults, type: typeName, optional, nullable, callbacks: girCallbacks, isArray: array.isArray, isFunction, isCallback, }) return tsType } /** * * @param girFunc * @returns */ private getReturnType( girFunc: | GirMethodElement | GirFunctionElement | GirConstructorElement | GirCallbackElement | GirSignalElement | GirVirtualMethodElement, tsClass: TsClass | null, generics: TsGenericParameter[] = [], ) { let outArrayLengthIndex = -1 if (girFunc['return-value'] && girFunc['return-value'].length > 1) { throw new Error('Several return values found!') } // There are no multiple return values, so we always use index 0 const girVar = girFunc['return-value']?.[0] || null // We still use an array to allow multiple return values for later const returnTypes: TsType[] = [] if (girVar) { returnTypes.push(this.getTsType(girVar, tsClass, { generics })) outArrayLengthIndex = girVar.array && girVar.array[0].$?.length ? Number(girVar.array[0].$.length) : -1 } else { returnTypes.push(this.girFactory.newTsType({ type: 'void', generics })) } const retTypeIsVoid = returnTypes.length === 1 && returnTypes[0].type === 'void' return { returnTypes, outArrayLengthIndex, retTypeIsVoid } } private arrayLengthIndexLookup(girVar: GirCallableParamElement): number { if (!girVar.array) return -1 const arr: GirArrayType = girVar.array[0] if (!arr.$) return -1 if (arr.$.length) { return parseInt(arr.$.length) } return -1 } private closureDataIndexLookup(girVar: GirCallableParamElement): number { if (!girVar.$.closure) return -1 return parseInt(girVar.$.closure) } private destroyDataIndexLookup(girVar: GirCallableParamElement): number { if (!girVar.$.destroy) return -1 return parseInt(girVar.$.destroy) } private processParams( params: GirCallableParamElement[], skip: GirCallableParamElement[], getIndex: (param: GirCallableParamElement) => number, ): void { for (const param of params) { const index = getIndex(param) if (index < 0) continue if (index >= params.length) continue skip.push(params[index]) } } /** * Checks if the parameter is nullable (which results in ` | null`). * * @param girVar girVar to test */ private typeIsNullable( girVar: | GirCallableParamElement | GirCallableReturn | GirAliasElement | GirFieldElement | GirConstantElement | GirPropertyElement, ): boolean { const a = (girVar as GirCallableParamElement).$ if (!a) return false const type: GirType | undefined = girVar.type?.[0] const cType = type?.$?.['c:type'] // UTF-8 string pointers can be null, e.g. `gchar*`, see https://github.com/gjsify/ts-for-gir/issues/108 if (type?.$?.name === 'utf8' && cType?.endsWith('*')) { return true } // If the default value is NULL, handle this as nullable if (a['default-value'] === 'NULL') return true // Ignore depreciated `allow-none` if one of the new implementation `optional` or `nullable` is set if (a.optional || a.nullable) { return girBool(a.nullable) } else { return girBool(a.nullable) || girBool(a['allow-none']) || girBool(a['null-ok']) } } /** * Checks if the parameter is optional (which results in `foo?: bar`). * @param girVar girVar to test */ private typeIsOptional( girVar: | GirCallableParamElement | GirCallableReturn | GirAliasElement | GirFieldElement | GirConstantElement | GirPropertyElement, ): boolean { const a = (girVar as GirCallableParamElement).$ if (!a) return false // Ignore depreciated `allow-none` if one of the new implementation `optional` or `nullable` is set if (a.optional || a.nullable) { return girBool(a.optional) } else { return girBool(a.optional) || girBool(a['allow-none']) || girBool(a['null-ok']) } } private setParameterTsData( girParam: GirCallableParamElement, girParams: GirCallableParamElement[], paramNames: string[], skip: GirCallableParamElement[], parent: TsFunction | TsSignal, ) { // I think it's safest to force inout params to have the // same type for in and out const tsType = this.getTsType(girParam, parent.parent) // const optDirection = girParam.$.direction if (girParam._tsData) { // this.log.warn('[setParameterTsData] _tsData already set!') return girParam._tsData } if (tsType.isCallback) { throw new Error('Callback type is not implemented here') } let paramName = this.transformation.transformParameterName(girParam, false) if (paramNames.includes(paramName)) { this.log.warn(WARN_DUPLICATE_PARAMETER(paramName, girParam._fullSymName)) paramName += '_' } paramNames.push(paramName) // In Typescript no optional parameters are allowed if the following ones are not optional if (tsType.optional) { const index = girParams.indexOf(girParam) const following = girParams .slice(index) .filter(() => skip.indexOf(girParam) === -1) .filter((p) => p.$.direction !== GirDirection.Out) if (following.some((p) => !this.typeIsOptional(p))) { tsType.optional = false } } const tsParam: TsParameter = { name: paramName, type: [tsType], isRest: false, girTypeName: 'callable-param', doc: this.getTsDoc(girParam), parent, } girParam._tsData = tsParam // // TODO: remove this, wee need a special solution for Gio.AsyncReadyCallback instead girParam = this.inject.toParameterType(girParam) return girParam._tsData } private getInstanceParameterTsData(instanceParameter: GirInstanceParameter): TsInstanceParameter | undefined { const typeName = instanceParameter.type?.[0]?.$?.name || undefined const rec = typeName ? this.ns.record?.find((r) => r.$.name == typeName) : undefined const structFor = rec?.$['glib:is-gtype-struct-for'] if (structFor && instanceParameter.$.name) { // TODO: Should use of a constructor, and even of an instance, be discouraged? return { name: instanceParameter.$.name, structFor, } } return undefined } private setParametersTsData( outArrayLengthIndex: number, girParams: GirCallableParams[] = [], parent: TsFunction | TsCallback, ) { const outParams: GirCallableParamElement[] = [] const inParams: GirCallableParamElement[] = [] const paramNames: string[] = [] const instanceParameters: GirInstanceParameter[] = [] if (girParams && girParams.length > 0) { for (const girParam of girParams) { const params = girParam?.parameter || [] // Instance parameter needs to be exposed for class methods (see comment above getClassMethods()) const instanceParameter = girParams[0]['instance-parameter']?.[0] if (instanceParameter) { instanceParameter._tsData = this.getInstanceParameterTsData(instanceParameter) if (instanceParameter._tsData) { instanceParameters.push(instanceParameter) } } if (params.length) { const skip = outArrayLengthIndex === -1 ? [] : [params[outArrayLengthIndex]] this.processParams(params, skip, (girVar) => this.arrayLengthIndexLookup(girVar)) this.processParams(params, skip, (girVar) => this.closureDataIndexLookup(girVar)) this.processParams(params, skip, (girVar) => this.destroyDataIndexLookup(girVar)) for (const param of params) { if (skip.includes(param)) { continue } param._tsData = this.setParameterTsData(param, params, paramNames, skip, parent) const optDirection = param.$.direction if ( optDirection === GirDirection.Out || optDirection === GirDirection.Inout || optDirection === GirDirection.InOut ) { outParams.push(param) if (optDirection === GirDirection.Out) continue } inParams.push(param) } } } } return { outParams, paramNames, inParams, instanceParameters } } private getVariableTsData( girVar: GirPropertyElement, girTypeName: 'property', tsTypeName: 'property' | 'constructor-property' | 'static-property', tsClass: TsClass | null, optional: boolean, nullable: boolean, allowQuotes: boolean, ): GirPropertyElement['_tsData'] private getVariableTsData( girVar: GirConstantElement, girTypeName: 'constant', tsTypeName: 'constant', tsClass: TsClass | null, optional: boolean, nullable: boolean, allowQuotes: boolean, ): GirConstantElement['_tsData'] private getVariableTsData( girVar: GirFieldElement, girTypeName: 'field', tsTypeName: 'property' | 'static-property', tsClass: TsClass | null, optional: boolean, nullable: boolean, allowQuotes: boolean, ): GirFieldElement['_tsData'] private getVariableTsData( girVar: GirPropertyElement | GirFieldElement | GirConstantElement, girTypeName: 'property' | TypeGirVariable | 'field', tsTypeName: 'constant' | 'property' | 'constructor-property' | 'static-property', tsClass: TsClass | null, optional = false, nullable = false, allowQuotes = false, generics: TsGenericParameter[] = [], ) { if (!girVar.$.name) return undefined if ( !girVar || !girVar.$ || !girBool(girVar.$.introspectable, true) || girBool((girVar as GirFieldElement).$.private) ) { return undefined } if (girVar._tsData) { // this.log.warn('[getVariableTsData] _tsData already set!') return girVar._tsData } let name = girVar.$.name switch (girTypeName) { case 'property': name = this.transformation.transformPropertyName(girVar.$.name, allowQuotes) break case 'constant': name = this.transformation.transformConstantName(girVar.$.name, allowQuotes) break case 'field': name = this.transformation.transformFieldName(girVar.$.name, allowQuotes) break } // Use the out type because it can be a union which isn't appropriate // for a property const tsType = this.getTsType(girVar, tsClass, { optional, nullable, generics }) const tsData: TsProperty | TsVar = { name, type: [tsType], isInjected: false, girTypeName, tsTypeName, doc: this.getTsDoc(girVar), } tsData.doc.tags.push(...this.getTsDocGirElementTags(tsData.tsTypeName, tsData.girTypeName)) return tsData } private getPropertyTsData( girProp: GirPropertyElement, girTypeName: 'property', tsTypeName: 'property' | 'constructor-property' | 'static-property', tsClass: TsClass, construct?: boolean, optional?: boolean, nullable?: boolean, indentCount?: number, ): TsProperty | undefined private getPropertyTsData( girProp: GirFieldElement, girTypeName: 'field', tsTypeName: 'property' | 'static-property', tsClass: TsClass, construct?: boolean, optional?: boolean, nullable?: boolean, indentCount?: number, ): TsVar | undefined /** * * @param girProp * @param girTypeName * @param tsTypeName * @param construct construct means include the property even if it's construct-only, * @param optional optional means if it's construct-only it will also be marked optional (?) * @param nullable * @returns */ private getPropertyTsData( girProp: GirPropertyElement | GirFieldElement, girTypeName: 'property' | 'field', tsTypeName: 'constructor-property' | 'property' | 'static-property', tsClass: TsClass, construct = false, optional?: boolean, nullable?: boolean, ): TsProperty | undefined { if (!girBool(girProp.$.writable) && construct) return undefined if (girBool((girProp as GirFieldElement).$.private)) return undefined if (girProp._tsData) { // this.log.warn('[getPropertyTsData] _tsData already set!') return girProp._tsData as TsProperty } if (optional === undefined) optional = this.typeIsOptional(girProp) if (nullable === undefined) nullable = this.typeIsNullable(girProp) const readonly = !girBool(girProp.$.writable) || (!construct && girBool((girProp as GirPropertyElement).$['construct-only'])) let tsData: TsProperty | undefined switch (girTypeName) { case 'property': tsData = this.getVariableTsData( girProp as GirPropertyElement, girTypeName, tsTypeName, tsClass, construct && optional, construct && nullable, true, ) as TsProperty break case 'field': if (tsTypeName !== 'property') { throw new Error(`Wrong tsType: "${tsTypeName}" for girType: "${girTypeName}`) } tsData = this.getVariableTsData( girProp as GirFieldElement, girTypeName, tsTypeName, tsClass, construct && optional, construct && nullable, true, ) as TsProperty break default: throw new Error(`Unknown property type: ${girTypeName as string}`) } if (!tsData?.name) { return undefined } tsData = { ...tsData, readonly, } return tsData } /** * * @param girFunc * @param prefix E.g. vfunc * @param overrideReturnType * @param isArrowType * @param indentCount */ private getFunctionTsData( girFunc: | GirMethodElement | GirFunctionElement | GirConstructorElement | GirCallbackElement | GirVirtualMethodElement, girTypeName: 'virtual' | 'method' | 'constructor' | 'function' | 'callback' | 'static-function', tsClass: TsClass | null, overwrite: { isStatic: boolean isArrowType: boolean isGlobal: boolean isVirtual: boolean isInjected?: boolean returnType: string | null generics: TsGenericParameter[] }, ): TsFunction | undefined { if (!girFunc || !girFunc.$ || !girBool(girFunc.$.introspectable, true) || girFunc.$['shadowed-by']) { return undefined } let hasUnresolvedConflict: boolean | undefined // TODO: Fix that we overwrite tsData every time seems wrong to me, but if I just return the already defined `_tsData` leads to problems with the overload methods if (girFunc._tsData) { hasUnresolvedConflict = girFunc._tsData?.hasUnresolvedConflict // WORKAROUND do not overwrite conflicts } let name = girFunc.$.name const { returnTypes, outArrayLengthIndex, retTypeIsVoid } = this.getReturnType(girFunc, tsClass) const shadows = (girFunc as GirMethodElement).$.shadows if (shadows) { name = shadows } // Overwrites overwrite.isStatic = overwrite.isStatic || girTypeName === 'static-function' || girTypeName === 'constructor' overwrite.isGlobal = overwrite.isGlobal || girTypeName === 'function' overwrite.isVirtual = overwrite.isVirtual || girTypeName === 'virtual' overwrite.isInjected = overwrite.isInjected || false // Function name transformation by environment name = this.transformation.transformFunctionName(name, overwrite.isVirtual) const tsData: TsFunction = { isArrowType: overwrite.isArrowType, isStatic: overwrite.isStatic, isGlobal: overwrite.isGlobal, isVirtual: overwrite.isVirtual, isInjected: overwrite.isInjected, returnTypes, retTypeIsVoid, name, overrideReturnType: overwrite.returnType || undefined, inParams: [], outParams: [], instanceParameters: [], generics: overwrite.generics, hasUnresolvedConflict, girTypeName, tsTypeName: this.girFactory.girTypeNameToTsTypeName(girTypeName, overwrite.isStatic), doc: this.getTsDoc(girFunc as GirDocElement), overloads: [], parent: tsClass, } const { inParams, outParams, instanceParameters } = this.setParametersTsData( outArrayLengthIndex, girFunc.parameters, tsData, ) tsData.inParams.push(...inParams) tsData.outParams.push(...outParams) tsData.instanceParameters.push(...instanceParameters) tsData.doc.tags.push(...this.getTsDocGirElementTags(tsData.tsTypeName, tsData.girTypeName)) tsData.doc.tags.push(...this.getTsDocInParamTags(tsData?.inParams)) tsData.doc.tags.push(...this.getTsDocReturnTags(girFunc)) return tsData } overloadPromisifiedFunctions(girFunctions: GirFunctionElement[]): void { if (!this.config.promisify) return const promisifyAsyncReturn = ['Gio.AsyncReadyCallback', 'AsyncReadyCallback'] const promisifyFuncMap = {} as { [name: string]: PromisifyFunc } // Find the functions that can be promisified for (const girFunction of girFunctions) { const tsFunction = girFunction._tsData if (!tsFunction) continue // Check if function name satisfies async,finish scheme const isAsync = tsFunction.name.endsWith('_async') || tsFunction.name.endsWith('_begin') const isFinish = tsFunction.name.endsWith('_finish') if (!isAsync && !isFinish) continue // Handle async functions if (isAsync) { if (tsFunction.inParams.length === 0) continue const lastParam = tsFunction.inParams[tsFunction.inParams.length - 1] if (lastParam.type && lastParam.type.length > 0) { const type = lastParam.type[0].$.name if (type && promisifyAsyncReturn.includes(type)) { if (!(tsFunction.name in promisifyFuncMap)) promisifyFuncMap[tsFunction.name] = {} promisifyFuncMap[tsFunction.name].asyncFn = tsFunction } } } // Handle finish functions if (isFinish) { if (tsFunction.returnTypes.length === 0) continue let name = `${tsFunction.name.replace(/(_finish)$/, '')}_async` if (!(name in promisifyFuncMap)) name = `${tsFunction.name.replace(/(_finish)$/, '')}_begin` if (!(name in promisifyFuncMap)) promisifyFuncMap[name] = {} promisifyFuncMap[name].finishFn = tsFunction } } // Generate TsFunctions for promisify-able functions and add to the array for (const [, func] of Object.entries(promisifyFuncMap)) { if (!func.asyncFn || !func.finishFn) continue const inParams = this.girFactory.newGirCallableParamElements( func.asyncFn.inParams.slice(0, -1), func.asyncFn, ) const outParams = this.girFactory.newGirCallableParamElements(func.finishFn.outParams, func.asyncFn) const returnTypes = this.girFactory.newTsTypes(outParams.length > 0 ? [] : func.finishFn.returnTypes) let docReturnText = func.finishFn.doc.tags.find((tag) => tag.tagName === 'returns')?.text || '' if (docReturnText) { docReturnText = `A Promise of: ${docReturnText}` } else { docReturnText = `A Promise of the result of {@link ${func.asyncFn.name}}` } const docText = `Promisified version of {@link ${func.asyncFn.name}}\n\n${func.asyncFn.doc.text}` const docTags = func.asyncFn.doc.tags.filter( (tag) => tag.paramName !== 'callback' && tag.paramName !== 'returns', ) docTags.push({ tagName: 'returns', text: docReturnText, paramName: '', }) const doc = this.girFactory.newTsDoc({ text: docText, tags: docTags, }) const promisifyFn = this.girFactory.newTsFunction( { ...func.asyncFn, inParams, outParams, returnTypes, isPromise: true, doc, }, func.asyncFn.parent, ) func.asyncFn.overloads.push(promisifyFn) } } private getCallbackInterfaceTsData(girCallback: GirCallbackElement | GirConstructorElement) { if (!girElementIsIntrospectable(girCallback)) return undefined const namespace = this.namespace const tsDataInterface: TsCallbackInterface = { name: `${namespace}.${girCallback.$.name}`, generics: [], } return tsDataInterface } private setCallbackTsData(girCallback: GirCallbackElement, tsClass: TsClass | null) { const tsFunction = this.getFunctionTsData(girCallback, 'callback', tsClass, { isStatic: false, isArrowType: true, isGlobal: false, isVirtual: false, returnType: null, generics: [], }) if (tsFunction) { const tsCallback: TsCallback = { ...tsFunction, girTypeName: 'callback', tsTypeName: this.girFactory.girTypeNameToTsTypeName('callback', false), tsCallbackInterface: this.getCallbackInterfaceTsData(girCallback), overloads: [], } girCallback._tsData = tsCallback this.inject.toCallback(girCallback) } return girCallback._tsData } private getSignalCallbackInterfaceTsData( girCallback: GirSignalElement, girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, ) { if (!girElementIsIntrospectable(girCallback)) return undefined if (!girClass._tsData || !girClass._module) { throw new Error(NO_TSDATA('getSignalCallbackTsData')) } const className = girClass._tsData.name const signalName = girCallback.$.name const signalInterfaceName = this.transformation.transformSignalInterfaceName(signalName) const namespace = girClass._module.namespace const tsDataInterface: TsCallbackInterface = { name: `${namespace}.${className}.${signalInterfaceName}SignalCallback`, generics: [], overwriteDoc: { text: `Signal callback interface for \`${signalName}\``, tags: [], }, } return tsDataInterface } private getConstructorFunctionTsData(parentClass: TsClass, girConstructor: GirConstructorElement) { if (!girElementIsIntrospectable(girConstructor)) return const constructorTypeName = addNamespace(parentClass.name, parentClass.namespace) return this.getFunctionTsData(girConstructor, 'constructor', parentClass, { isStatic: true, isArrowType: false, isGlobal: false, isVirtual: false, returnType: constructorTypeName, generics: [], }) } private getSignalCallbackTsData( girSignalFunc: GirSignalElement, girClass: GirClassElement | GirUnionElement | GirInterfaceElement | GirRecordElement, ) { if (!girClass._tsData) { throw new Error(NO_TSDATA('getSignalCallbackTsData')) } if (girSignalFunc._tsData) { return girSignalFunc._tsData } // Leads to errors here // if (!girElementIsIntrospectable(girSignalFunc)) return undefined con