@gtkx/gir 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@gtkx/gir",
3
+ "version": "0.1.0",
4
+ "description": "GObject Introspection file parser for GTKX",
5
+ "license": "MPL-2.0",
6
+ "author": "Eugenio Depalo <eugeniodepalo@gmail.com>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/eugeniodepalo/gtkx.git",
10
+ "directory": "packages/gir"
11
+ },
12
+ "homepage": "https://eugeniodepalo.github.io/gtkx",
13
+ "bugs": "https://github.com/eugeniodepalo/gtkx/issues",
14
+ "keywords": ["gtk", "gtk4", "gir", "gobject", "introspection", "parser"],
15
+ "type": "module",
16
+ "exports": {
17
+ "./package.json": "./package.json",
18
+ ".": "./src/index.ts"
19
+ },
20
+ "files": ["src"]
21
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./parser.js";
2
+ export * from "./types.js";
package/src/parser.ts ADDED
@@ -0,0 +1,313 @@
1
+ import { XMLParser } from "fast-xml-parser";
2
+ import type {
3
+ GirClass,
4
+ GirConstructor,
5
+ GirEnumeration,
6
+ GirEnumerationMember,
7
+ GirFunction,
8
+ GirInterface,
9
+ GirMethod,
10
+ GirNamespace,
11
+ GirParameter,
12
+ GirProperty,
13
+ GirSignal,
14
+ GirType,
15
+ } from "./types.js";
16
+
17
+ const ARRAY_ELEMENT_PATHS = new Set<string>([
18
+ "namespace.class",
19
+ "namespace.interface",
20
+ "namespace.function",
21
+ "namespace.enumeration",
22
+ "namespace.bitfield",
23
+ "namespace.class.method",
24
+ "namespace.class.constructor",
25
+ "namespace.class.property",
26
+ "namespace.class.signal",
27
+ "namespace.class.glib:signal",
28
+ "namespace.interface.method",
29
+ "namespace.interface.property",
30
+ "namespace.interface.signal",
31
+ "namespace.interface.glib:signal",
32
+ "namespace.class.method.parameters.parameter",
33
+ "namespace.class.constructor.parameters.parameter",
34
+ "namespace.function.parameters.parameter",
35
+ "namespace.enumeration.member",
36
+ "namespace.bitfield.member",
37
+ "namespace.interface.method.parameters.parameter",
38
+ "namespace.class.glib:signal.parameters.parameter",
39
+ "namespace.interface.glib:signal.parameters.parameter",
40
+ ]);
41
+
42
+ const extractDoc = (node: Record<string, unknown>): string | undefined => {
43
+ const doc = node.doc as Record<string, unknown> | undefined;
44
+ if (!doc) return undefined;
45
+ const text = doc["#text"] as string | undefined;
46
+ return text?.trim();
47
+ };
48
+
49
+ /**
50
+ * Parser for GObject Introspection (GIR) XML files.
51
+ * Converts GIR XML into structured TypeScript interfaces.
52
+ */
53
+ export class GirParser {
54
+ private parser: XMLParser;
55
+
56
+ /** Creates a new GIR parser instance. */
57
+ constructor() {
58
+ this.parser = new XMLParser({
59
+ ignoreAttributes: false,
60
+ attributeNamePrefix: "@_",
61
+ textNodeName: "#text",
62
+ isArray: (_name, jpath, _isLeafNode, _isAttribute) => {
63
+ const path = jpath.split(".").slice(1).join(".");
64
+ return ARRAY_ELEMENT_PATHS.has(path);
65
+ },
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Parses a GIR XML string into a structured namespace definition.
71
+ * @param girXml - The GIR XML content to parse
72
+ * @returns The parsed namespace containing all type definitions
73
+ * @throws Error if the XML is invalid or missing required elements
74
+ */
75
+ parse(girXml: string): GirNamespace {
76
+ const parsed = this.parser.parse(girXml);
77
+ const repository = parsed.repository;
78
+
79
+ if (!repository?.namespace) {
80
+ throw new Error("Invalid GIR file: missing repository or namespace");
81
+ }
82
+
83
+ const namespace = repository.namespace;
84
+
85
+ return {
86
+ name: namespace["@_name"],
87
+ version: namespace["@_version"],
88
+ sharedLibrary: namespace["@_shared-library"] ?? "",
89
+ cPrefix: namespace["@_c:identifier-prefixes"] ?? namespace["@_c:prefix"] ?? "",
90
+ classes: this.parseClasses(namespace.class ?? []),
91
+ interfaces: this.parseInterfaces(namespace.interface ?? []),
92
+ functions: this.parseFunctions(namespace.function ?? []),
93
+ enumerations: this.parseEnumerations(namespace.enumeration ?? []),
94
+ bitfields: this.parseEnumerations(namespace.bitfield ?? []),
95
+ };
96
+ }
97
+
98
+ private parseClasses(classes: Record<string, unknown>[]): GirClass[] {
99
+ return classes.map((cls) => ({
100
+ name: String(cls["@_name"] ?? ""),
101
+ cType: String(cls["@_c:type"] ?? cls["@_glib:type-name"] ?? ""),
102
+ parent: String(cls["@_parent"] ?? ""),
103
+ abstract: cls["@_abstract"] === "1",
104
+ implements: this.parseImplements(
105
+ cls.implements as Record<string, unknown>[] | Record<string, unknown> | undefined,
106
+ ),
107
+ methods: this.parseMethods(Array.isArray(cls.method) ? (cls.method as Record<string, unknown>[]) : []),
108
+ constructors: this.parseConstructors(
109
+ Array.isArray(cls.constructor) ? (cls.constructor as Record<string, unknown>[]) : [],
110
+ ),
111
+ properties: this.parseProperties(
112
+ Array.isArray(cls.property) ? (cls.property as Record<string, unknown>[]) : [],
113
+ ),
114
+ signals: this.parseSignals(
115
+ Array.isArray(cls["glib:signal"]) ? (cls["glib:signal"] as Record<string, unknown>[]) : [],
116
+ ),
117
+ doc: extractDoc(cls),
118
+ }));
119
+ }
120
+
121
+ private parseImplements(implements_: Record<string, unknown>[] | Record<string, unknown> | undefined): string[] {
122
+ if (!implements_) return [];
123
+ const arr = Array.isArray(implements_) ? implements_ : [implements_];
124
+ return arr.map((impl) => String(impl["@_name"] ?? "")).filter(Boolean);
125
+ }
126
+
127
+ private parseInterfaces(interfaces: Record<string, unknown>[]): GirInterface[] {
128
+ if (!interfaces || !Array.isArray(interfaces)) {
129
+ return [];
130
+ }
131
+ return interfaces.map((iface) => ({
132
+ name: String(iface["@_name"] ?? ""),
133
+ cType: String(iface["@_c:type"] ?? iface["@_glib:type-name"] ?? ""),
134
+ methods: this.parseMethods(Array.isArray(iface.method) ? (iface.method as Record<string, unknown>[]) : []),
135
+ properties: this.parseProperties(
136
+ Array.isArray(iface.property) ? (iface.property as Record<string, unknown>[]) : [],
137
+ ),
138
+ signals: this.parseSignals(
139
+ Array.isArray(iface["glib:signal"]) ? (iface["glib:signal"] as Record<string, unknown>[]) : [],
140
+ ),
141
+ doc: extractDoc(iface),
142
+ }));
143
+ }
144
+
145
+ private parseMethods(methods: Record<string, unknown>[]): GirMethod[] {
146
+ if (!methods || !Array.isArray(methods)) {
147
+ return [];
148
+ }
149
+ return methods.map((method) => ({
150
+ name: String(method["@_name"] ?? ""),
151
+ cIdentifier: String(method["@_c:identifier"] ?? ""),
152
+ returnType: this.parseReturnType(method["return-value"] as Record<string, unknown> | undefined),
153
+ parameters: this.parseParameters(
154
+ (method.parameters && typeof method.parameters === "object" && method.parameters !== null
155
+ ? method.parameters
156
+ : {}) as Record<string, unknown>,
157
+ ),
158
+ throws: method["@_throws"] === "1",
159
+ doc: extractDoc(method),
160
+ }));
161
+ }
162
+
163
+ private parseConstructors(constructors: Record<string, unknown>[]): GirConstructor[] {
164
+ if (!constructors || !Array.isArray(constructors)) {
165
+ return [];
166
+ }
167
+ return constructors.map((ctor) => ({
168
+ name: String(ctor["@_name"] ?? ""),
169
+ cIdentifier: String(ctor["@_c:identifier"] ?? ""),
170
+ returnType: this.parseReturnType(ctor["return-value"] as Record<string, unknown> | undefined),
171
+ parameters: this.parseParameters(
172
+ (ctor.parameters && typeof ctor.parameters === "object" && ctor.parameters !== null
173
+ ? ctor.parameters
174
+ : {}) as Record<string, unknown>,
175
+ ),
176
+ doc: extractDoc(ctor),
177
+ }));
178
+ }
179
+
180
+ private parseFunctions(functions: Record<string, unknown>[]): GirFunction[] {
181
+ if (!functions || !Array.isArray(functions)) {
182
+ return [];
183
+ }
184
+ return functions.map((func) => ({
185
+ name: String(func["@_name"] ?? ""),
186
+ cIdentifier: String(func["@_c:identifier"] ?? ""),
187
+ returnType: this.parseReturnType(func["return-value"] as Record<string, unknown> | undefined),
188
+ parameters: this.parseParameters(
189
+ (func.parameters && typeof func.parameters === "object" && func.parameters !== null
190
+ ? func.parameters
191
+ : {}) as Record<string, unknown>,
192
+ ),
193
+ throws: func["@_throws"] === "1",
194
+ doc: extractDoc(func),
195
+ }));
196
+ }
197
+
198
+ private parseParameters(parametersNode: Record<string, unknown>): GirParameter[] {
199
+ if (!parametersNode?.parameter) {
200
+ return [];
201
+ }
202
+
203
+ const params = Array.isArray(parametersNode.parameter) ? parametersNode.parameter : [parametersNode.parameter];
204
+
205
+ return params.map((param: Record<string, unknown>) => ({
206
+ name: String(param["@_name"] ?? ""),
207
+ type: this.parseType((param.type ?? param.array) as Record<string, unknown> | undefined),
208
+ direction: (String(param["@_direction"] ?? "in") as "in" | "out" | "inout") || "in",
209
+ nullable: param["@_nullable"] === "1",
210
+ optional: param["@_allow-none"] === "1",
211
+ doc: extractDoc(param),
212
+ }));
213
+ }
214
+
215
+ private parseReturnType(returnValue: Record<string, unknown> | undefined): GirType {
216
+ if (!returnValue) {
217
+ return { name: "void" };
218
+ }
219
+ return this.parseType((returnValue.type ?? returnValue.array) as Record<string, unknown> | undefined);
220
+ }
221
+
222
+ private parseType(typeNode: Record<string, unknown> | undefined): GirType {
223
+ if (!typeNode) {
224
+ return { name: "void" };
225
+ }
226
+
227
+ if (typeNode["@_name"]) {
228
+ return {
229
+ name: String(typeNode["@_name"] ?? ""),
230
+ cType: typeNode["@_c:type"] ? String(typeNode["@_c:type"]) : undefined,
231
+ };
232
+ }
233
+
234
+ const isArrayNode =
235
+ typeNode.type ||
236
+ typeNode["@_zero-terminated"] !== undefined ||
237
+ typeNode["@_fixed-size"] !== undefined ||
238
+ typeNode["@_length"] !== undefined;
239
+
240
+ if (isArrayNode) {
241
+ return {
242
+ name: "array",
243
+ isArray: true,
244
+ elementType: typeNode.type ? this.parseType(typeNode.type as Record<string, unknown>) : undefined,
245
+ };
246
+ }
247
+
248
+ return { name: "void" };
249
+ }
250
+
251
+ private parseProperties(properties: Record<string, unknown>[]): GirProperty[] {
252
+ if (!properties || !Array.isArray(properties)) {
253
+ return [];
254
+ }
255
+ return properties.map((prop) => ({
256
+ name: String(prop["@_name"] ?? ""),
257
+ type: this.parseType((prop.type ?? prop.array) as Record<string, unknown> | undefined),
258
+ readable: prop["@_readable"] !== "0",
259
+ writable: prop["@_writable"] === "1",
260
+ constructOnly: prop["@_construct-only"] === "1",
261
+ hasDefault: prop["@_default-value"] !== undefined,
262
+ doc: extractDoc(prop),
263
+ }));
264
+ }
265
+
266
+ private parseSignals(signals: Record<string, unknown>[]): GirSignal[] {
267
+ if (!signals || !Array.isArray(signals)) {
268
+ return [];
269
+ }
270
+ return signals.map((signal) => {
271
+ const whenValue = String(signal["@_when"] ?? "last");
272
+ const validWhen = whenValue === "first" || whenValue === "last" || whenValue === "cleanup";
273
+ return {
274
+ name: String(signal["@_name"] ?? ""),
275
+ when: validWhen ? (whenValue as "first" | "last" | "cleanup") : "last",
276
+ returnType: signal["return-value"]
277
+ ? this.parseReturnType(signal["return-value"] as Record<string, unknown>)
278
+ : undefined,
279
+ parameters:
280
+ signal.parameters && typeof signal.parameters === "object" && signal.parameters !== null
281
+ ? this.parseParameters(signal.parameters as Record<string, unknown>)
282
+ : [],
283
+ doc: extractDoc(signal),
284
+ };
285
+ });
286
+ }
287
+
288
+ private parseEnumerations(enumerations: Record<string, unknown>[]): GirEnumeration[] {
289
+ if (!enumerations || !Array.isArray(enumerations)) {
290
+ return [];
291
+ }
292
+ return enumerations.map((enumeration) => ({
293
+ name: String(enumeration["@_name"] ?? ""),
294
+ cType: String(enumeration["@_c:type"] ?? ""),
295
+ members: this.parseEnumerationMembers(
296
+ Array.isArray(enumeration.member) ? (enumeration.member as Record<string, unknown>[]) : [],
297
+ ),
298
+ doc: extractDoc(enumeration),
299
+ }));
300
+ }
301
+
302
+ private parseEnumerationMembers(members: Record<string, unknown>[]): GirEnumerationMember[] {
303
+ if (!members || !Array.isArray(members)) {
304
+ return [];
305
+ }
306
+ return members.map((member) => ({
307
+ name: String(member["@_name"] ?? ""),
308
+ value: String(member["@_value"] ?? ""),
309
+ cIdentifier: String(member["@_c:identifier"] ?? ""),
310
+ doc: extractDoc(member),
311
+ }));
312
+ }
313
+ }
package/src/types.ts ADDED
@@ -0,0 +1,469 @@
1
+ /**
2
+ * Represents a parsed GIR namespace containing all type definitions.
3
+ */
4
+ export interface GirNamespace {
5
+ /** The namespace name (e.g., "Gtk", "Gio"). */
6
+ name: string;
7
+ /** The namespace version (e.g., "4.0"). */
8
+ version: string;
9
+ /** The shared library file name. */
10
+ sharedLibrary: string;
11
+ /** The C identifier prefix for this namespace. */
12
+ cPrefix: string;
13
+ /** All classes defined in this namespace. */
14
+ classes: GirClass[];
15
+ /** All interfaces defined in this namespace. */
16
+ interfaces: GirInterface[];
17
+ /** All standalone functions defined in this namespace. */
18
+ functions: GirFunction[];
19
+ /** All enumerations defined in this namespace. */
20
+ enumerations: GirEnumeration[];
21
+ /** All bitfield enumerations defined in this namespace. */
22
+ bitfields: GirEnumeration[];
23
+ /** Documentation for the namespace. */
24
+ doc?: string;
25
+ }
26
+
27
+ /**
28
+ * Represents a GIR interface definition.
29
+ */
30
+ export interface GirInterface {
31
+ /** The interface name. */
32
+ name: string;
33
+ /** The C type name. */
34
+ cType: string;
35
+ /** Methods defined on this interface. */
36
+ methods: GirMethod[];
37
+ /** Properties defined on this interface. */
38
+ properties: GirProperty[];
39
+ /** Signals defined on this interface. */
40
+ signals: GirSignal[];
41
+ /** Documentation for the interface. */
42
+ doc?: string;
43
+ }
44
+
45
+ /**
46
+ * Represents a GIR class definition.
47
+ */
48
+ export interface GirClass {
49
+ /** The class name. */
50
+ name: string;
51
+ /** The C type name. */
52
+ cType: string;
53
+ /** The parent class name, if any. */
54
+ parent?: string;
55
+ /** Whether this is an abstract class. */
56
+ abstract?: boolean;
57
+ /** List of interface names this class implements. */
58
+ implements: string[];
59
+ /** Methods defined on this class. */
60
+ methods: GirMethod[];
61
+ /** Constructor functions for this class. */
62
+ constructors: GirConstructor[];
63
+ /** Properties defined on this class. */
64
+ properties: GirProperty[];
65
+ /** Signals defined on this class. */
66
+ signals: GirSignal[];
67
+ /** Documentation for the class. */
68
+ doc?: string;
69
+ }
70
+
71
+ /**
72
+ * Represents a GIR method definition.
73
+ */
74
+ export interface GirMethod {
75
+ /** The method name. */
76
+ name: string;
77
+ /** The C function identifier. */
78
+ cIdentifier: string;
79
+ /** The return type. */
80
+ returnType: GirType;
81
+ /** The method parameters. */
82
+ parameters: GirParameter[];
83
+ /** Whether this method can throw a GError. */
84
+ throws?: boolean;
85
+ /** Documentation for the method. */
86
+ doc?: string;
87
+ }
88
+
89
+ /**
90
+ * Represents a GIR constructor definition.
91
+ */
92
+ export interface GirConstructor {
93
+ /** The constructor name. */
94
+ name: string;
95
+ /** The C function identifier. */
96
+ cIdentifier: string;
97
+ /** The return type (typically the class type). */
98
+ returnType: GirType;
99
+ /** The constructor parameters. */
100
+ parameters: GirParameter[];
101
+ /** Documentation for the constructor. */
102
+ doc?: string;
103
+ }
104
+
105
+ /**
106
+ * Represents a GIR standalone function definition.
107
+ */
108
+ export interface GirFunction {
109
+ /** The function name. */
110
+ name: string;
111
+ /** The C function identifier. */
112
+ cIdentifier: string;
113
+ /** The return type. */
114
+ returnType: GirType;
115
+ /** The function parameters. */
116
+ parameters: GirParameter[];
117
+ /** Whether this function can throw a GError. */
118
+ throws?: boolean;
119
+ /** Documentation for the function. */
120
+ doc?: string;
121
+ }
122
+
123
+ /**
124
+ * Represents a GIR parameter definition.
125
+ */
126
+ export interface GirParameter {
127
+ /** The parameter name. */
128
+ name: string;
129
+ /** The parameter type. */
130
+ type: GirType;
131
+ /** The parameter direction (in, out, or inout). */
132
+ direction?: "in" | "out" | "inout";
133
+ /** Whether this parameter can be null. */
134
+ nullable?: boolean;
135
+ /** Whether this parameter is optional. */
136
+ optional?: boolean;
137
+ /** Documentation for the parameter. */
138
+ doc?: string;
139
+ }
140
+
141
+ /**
142
+ * Represents a GIR type reference.
143
+ */
144
+ export interface GirType {
145
+ /** The type name. */
146
+ name: string;
147
+ /** The C type name. */
148
+ cType?: string;
149
+ /** Whether this is an array type. */
150
+ isArray?: boolean;
151
+ /** The element type for array types. */
152
+ elementType?: GirType;
153
+ }
154
+
155
+ /**
156
+ * Represents a GIR property definition.
157
+ */
158
+ export interface GirProperty {
159
+ /** The property name. */
160
+ name: string;
161
+ /** The property type. */
162
+ type: GirType;
163
+ /** Whether this property is readable. */
164
+ readable?: boolean;
165
+ /** Whether this property is writable. */
166
+ writable?: boolean;
167
+ /** Whether this property can only be set at construction time. */
168
+ constructOnly?: boolean;
169
+ /** Whether this property has a default value. */
170
+ hasDefault?: boolean;
171
+ /** Documentation for the property. */
172
+ doc?: string;
173
+ }
174
+
175
+ /**
176
+ * Represents a GIR signal definition.
177
+ */
178
+ export interface GirSignal {
179
+ /** The signal name. */
180
+ name: string;
181
+ /** When the signal handler runs relative to the default handler. */
182
+ when?: "first" | "last" | "cleanup";
183
+ /** The signal return type. */
184
+ returnType?: GirType;
185
+ /** The signal parameters passed to handlers. */
186
+ parameters?: GirParameter[];
187
+ /** Documentation for the signal. */
188
+ doc?: string;
189
+ }
190
+
191
+ /**
192
+ * Represents a GIR enumeration or bitfield definition.
193
+ */
194
+ export interface GirEnumeration {
195
+ /** The enumeration name. */
196
+ name: string;
197
+ /** The C type name. */
198
+ cType: string;
199
+ /** The enumeration members. */
200
+ members: GirEnumerationMember[];
201
+ /** Documentation for the enumeration. */
202
+ doc?: string;
203
+ }
204
+
205
+ /**
206
+ * Represents a single enumeration member.
207
+ */
208
+ export interface GirEnumerationMember {
209
+ /** The member name. */
210
+ name: string;
211
+ /** The numeric value. */
212
+ value: string;
213
+ /** The C identifier. */
214
+ cIdentifier: string;
215
+ /** Documentation for the member. */
216
+ doc?: string;
217
+ }
218
+
219
+ /**
220
+ * Describes an FFI type for code generation.
221
+ */
222
+ export interface FfiTypeDescriptor {
223
+ /** The FFI type category. */
224
+ type: string;
225
+ /** Size in bits for integer/float types. */
226
+ size?: number;
227
+ /** Whether the integer type is unsigned. */
228
+ unsigned?: boolean;
229
+ /** Whether the pointer is borrowed (not owned). */
230
+ borrowed?: boolean;
231
+ /** Inner type for ref types. */
232
+ innerType?: FfiTypeDescriptor;
233
+ /** Item type for array types. */
234
+ itemType?: FfiTypeDescriptor;
235
+ }
236
+
237
+ /**
238
+ * Converts a snake_case or kebab-case string to camelCase.
239
+ * @param str - The input string
240
+ * @returns The camelCase version
241
+ */
242
+ export const toCamelCase = (str: string): string => str.replace(/[-_]([a-z])/g, (_, letter) => letter.toUpperCase());
243
+
244
+ /**
245
+ * Converts a snake_case or kebab-case string to PascalCase.
246
+ * @param str - The input string
247
+ * @returns The PascalCase version
248
+ */
249
+ export const toPascalCase = (str: string): string => {
250
+ const camel = toCamelCase(str);
251
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
252
+ };
253
+
254
+ /**
255
+ * Builds a map of class names to class definitions for quick lookup.
256
+ * @param classes - Array of GIR class definitions
257
+ * @returns Map from class name to class definition
258
+ */
259
+ export const buildClassMap = (classes: GirClass[]): Map<string, GirClass> => {
260
+ const classMap = new Map<string, GirClass>();
261
+ for (const cls of classes) {
262
+ classMap.set(cls.name, cls);
263
+ }
264
+ return classMap;
265
+ };
266
+
267
+ /**
268
+ * Registers all enumerations and bitfields from a namespace with a type mapper.
269
+ * @param typeMapper - The TypeMapper instance to register with
270
+ * @param namespace - The GIR namespace containing enums to register
271
+ */
272
+ export const registerEnumsFromNamespace = (typeMapper: TypeMapper, namespace: GirNamespace): void => {
273
+ for (const enumeration of namespace.enumerations) {
274
+ typeMapper.registerEnum(enumeration.name, toPascalCase(enumeration.name));
275
+ }
276
+ for (const bitfield of namespace.bitfields) {
277
+ typeMapper.registerEnum(bitfield.name, toPascalCase(bitfield.name));
278
+ }
279
+ };
280
+
281
+ type TypeMapping = { ts: string; ffi: FfiTypeDescriptor };
282
+
283
+ const BASIC_TYPE_MAP = new Map<string, TypeMapping>([
284
+ ["gboolean", { ts: "boolean", ffi: { type: "boolean" } }],
285
+ ["gchar", { ts: "number", ffi: { type: "int", size: 8, unsigned: false } }],
286
+ ["guchar", { ts: "number", ffi: { type: "int", size: 8, unsigned: true } }],
287
+ ["gint", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
288
+ ["guint", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
289
+ ["gshort", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
290
+ ["gushort", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
291
+ ["glong", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
292
+ ["gulong", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
293
+ ["GType", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
294
+ ["gint8", { ts: "number", ffi: { type: "int", size: 8, unsigned: false } }],
295
+ ["guint8", { ts: "number", ffi: { type: "int", size: 8, unsigned: true } }],
296
+ ["gint16", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
297
+ ["guint16", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
298
+ ["gint32", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
299
+ ["guint32", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
300
+ ["gint64", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
301
+ ["guint64", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
302
+ ["gfloat", { ts: "number", ffi: { type: "float", size: 32 } }],
303
+ ["gdouble", { ts: "number", ffi: { type: "float", size: 64 } }],
304
+ ["utf8", { ts: "string", ffi: { type: "string" } }],
305
+ ["filename", { ts: "string", ffi: { type: "string" } }],
306
+ ["gpointer", { ts: "unknown", ffi: { type: "gobject" } }],
307
+ ["gconstpointer", { ts: "unknown", ffi: { type: "gobject" } }],
308
+ ["void", { ts: "void", ffi: { type: "undefined" } }],
309
+ ["none", { ts: "void", ffi: { type: "undefined" } }],
310
+ ["int", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
311
+ ["uint", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
312
+ ["long", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
313
+ ["ulong", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
314
+ ["size_t", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
315
+ ["ssize_t", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
316
+ ["double", { ts: "number", ffi: { type: "float", size: 64 } }],
317
+ ["float", { ts: "number", ffi: { type: "float", size: 32 } }],
318
+ ]);
319
+
320
+ const LIBRARY_MAP: Record<string, string> = {
321
+ Gtk: "libgtk-4.so.1",
322
+ GObject: "libgobject-2.0.so.0",
323
+ GLib: "libglib-2.0.so.0",
324
+ Gio: "libgio-2.0.so.0",
325
+ GdkPixbuf: "libgdk_pixbuf-2.0.so.0",
326
+ Pango: "libpango-1.0.so.0",
327
+ Cairo: "libcairo.so.2",
328
+ };
329
+
330
+ /**
331
+ * Maps GIR types to TypeScript types and FFI type descriptors.
332
+ * Handles basic types, enumerations, arrays, and object references.
333
+ */
334
+ export class TypeMapper {
335
+ private enumNames: Set<string> = new Set();
336
+ private enumTransforms: Map<string, string> = new Map();
337
+ private onEnumUsed?: (enumName: string) => void;
338
+
339
+ /**
340
+ * Registers an enumeration type for mapping.
341
+ * @param originalName - The original GIR enum name
342
+ * @param transformedName - The transformed TypeScript enum name
343
+ */
344
+ registerEnum(originalName: string, transformedName?: string): void {
345
+ this.enumNames.add(originalName);
346
+ if (transformedName) {
347
+ this.enumTransforms.set(originalName, transformedName);
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Sets a callback to track enum usage during type mapping.
353
+ * @param callback - Called when an enum is used, or null to clear
354
+ */
355
+ setEnumUsageCallback(callback: ((enumName: string) => void) | null): void {
356
+ this.onEnumUsed = callback ?? undefined;
357
+ }
358
+
359
+ /**
360
+ * Gets the current enum usage callback.
361
+ * @returns The callback or null if not set
362
+ */
363
+ getEnumUsageCallback(): ((enumName: string) => void) | null {
364
+ return this.onEnumUsed ?? null;
365
+ }
366
+
367
+ /**
368
+ * Maps a GIR type to TypeScript and FFI type descriptors.
369
+ * @param girType - The GIR type to map
370
+ * @param isReturn - Whether this is a return type (affects pointer ownership)
371
+ * @returns The TypeScript type string and FFI descriptor
372
+ */
373
+ mapType(girType: GirType, isReturn = false): TypeMapping {
374
+ if (girType.isArray || girType.name === "array") {
375
+ if (girType.elementType) {
376
+ const elementType = this.mapType(girType.elementType);
377
+ return {
378
+ ts: `${elementType.ts}[]`,
379
+ ffi: { type: "array", itemType: elementType.ffi },
380
+ };
381
+ }
382
+ return {
383
+ ts: `unknown[]`,
384
+ ffi: { type: "array", itemType: { type: "undefined" } },
385
+ };
386
+ }
387
+
388
+ const basicType = BASIC_TYPE_MAP.get(girType.name);
389
+ if (basicType) {
390
+ return basicType;
391
+ }
392
+
393
+ if (this.enumNames.has(girType.name)) {
394
+ const transformedName = this.enumTransforms.get(girType.name) ?? girType.name;
395
+ this.onEnumUsed?.(transformedName);
396
+ return {
397
+ ts: transformedName,
398
+ ffi: { type: "int", size: 32, unsigned: false },
399
+ };
400
+ }
401
+
402
+ if (girType.name.includes(".")) {
403
+ const [_ns, typeName] = girType.name.split(".", 2);
404
+ if (typeName && this.enumNames.has(typeName)) {
405
+ const transformedName = this.enumTransforms.get(typeName) ?? typeName;
406
+ this.onEnumUsed?.(transformedName);
407
+ return {
408
+ ts: transformedName,
409
+ ffi: { type: "int", size: 32, unsigned: false },
410
+ };
411
+ }
412
+ return {
413
+ ts: "unknown",
414
+ ffi: { type: "gobject", borrowed: isReturn },
415
+ };
416
+ }
417
+
418
+ return {
419
+ ts: "unknown",
420
+ ffi: { type: "gobject", borrowed: isReturn },
421
+ };
422
+ }
423
+
424
+ /**
425
+ * Maps a GIR parameter to TypeScript and FFI type descriptors.
426
+ * Handles out/inout parameters by wrapping in Ref type.
427
+ * @param param - The GIR parameter to map
428
+ * @returns The TypeScript type string and FFI descriptor
429
+ */
430
+ mapParameter(param: GirParameter): TypeMapping {
431
+ if (param.direction === "out" || param.direction === "inout") {
432
+ const innerType = this.mapType(param.type);
433
+ return {
434
+ ts: `Ref<${innerType.ts}>`,
435
+ ffi: {
436
+ type: "ref",
437
+ innerType: innerType.ffi,
438
+ },
439
+ };
440
+ }
441
+
442
+ if (param.type.name === "GLib.Closure" || param.type.name.endsWith("Func")) {
443
+ return {
444
+ ts: "(...args: unknown[]) => unknown",
445
+ ffi: { type: "callback" },
446
+ };
447
+ }
448
+
449
+ return this.mapType(param.type);
450
+ }
451
+
452
+ /**
453
+ * Checks if a parameter can accept null values.
454
+ * @param param - The parameter to check
455
+ * @returns True if the parameter is nullable or optional
456
+ */
457
+ isNullable(param: GirParameter): boolean {
458
+ return param.nullable === true || param.optional === true;
459
+ }
460
+
461
+ /**
462
+ * Gets the shared library name for a namespace.
463
+ * @param namespace - The namespace name
464
+ * @returns The shared library file name
465
+ */
466
+ getLibraryName(namespace: string): string {
467
+ return LIBRARY_MAP[namespace] ?? `lib${namespace.toLowerCase()}.so`;
468
+ }
469
+ }