@gtkx/gir 0.9.1 → 0.9.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/gir",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "GObject Introspection file parser for GTKX",
5
5
  "keywords": [
6
6
  "gtk",
@@ -0,0 +1,370 @@
1
+ type GirLinkType =
2
+ | "class"
3
+ | "iface"
4
+ | "struct"
5
+ | "enum"
6
+ | "flags"
7
+ | "error"
8
+ | "callback"
9
+ | "method"
10
+ | "vfunc"
11
+ | "func"
12
+ | "ctor"
13
+ | "property"
14
+ | "signal"
15
+ | "const"
16
+ | "type"
17
+ | "id";
18
+
19
+ interface GirLink {
20
+ type: GirLinkType;
21
+ namespace: string | undefined;
22
+ target: string;
23
+ member: string | undefined;
24
+ }
25
+
26
+ const GIR_LINK_PATTERN = /\[([a-z]+)@([^\]]+)\]/gi;
27
+
28
+ function parseGirLink(type: string, reference: string): GirLink | undefined {
29
+ const linkType = type.toLowerCase() as GirLinkType;
30
+
31
+ const validTypes: GirLinkType[] = [
32
+ "class",
33
+ "iface",
34
+ "struct",
35
+ "enum",
36
+ "flags",
37
+ "error",
38
+ "callback",
39
+ "method",
40
+ "vfunc",
41
+ "func",
42
+ "ctor",
43
+ "property",
44
+ "signal",
45
+ "const",
46
+ "type",
47
+ "id",
48
+ ];
49
+
50
+ if (!validTypes.includes(linkType)) {
51
+ return undefined;
52
+ }
53
+
54
+ const parts = reference.split(".");
55
+ if (parts.length === 0) {
56
+ return undefined;
57
+ }
58
+
59
+ if (linkType === "property" || linkType === "signal") {
60
+ const colonIndex = reference.indexOf(":");
61
+ if (colonIndex !== -1) {
62
+ const beforeColon = reference.substring(0, colonIndex);
63
+ const afterColon = reference.substring(colonIndex + 1);
64
+ const beforeParts = beforeColon.split(".");
65
+ if (beforeParts.length >= 2) {
66
+ return {
67
+ type: linkType,
68
+ namespace: beforeParts[0],
69
+ target: beforeParts.slice(1).join("."),
70
+ member: afterColon.replace("::", ""),
71
+ };
72
+ }
73
+ return {
74
+ type: linkType,
75
+ namespace: undefined,
76
+ target: beforeColon,
77
+ member: afterColon.replace("::", ""),
78
+ };
79
+ }
80
+ }
81
+
82
+ if (parts.length === 1) {
83
+ return {
84
+ type: linkType,
85
+ namespace: undefined,
86
+ target: parts[0] ?? "",
87
+ member: undefined,
88
+ };
89
+ }
90
+
91
+ if (parts.length === 2) {
92
+ const first = parts[0] ?? "";
93
+ const second = parts[1] ?? "";
94
+ const isNamespace = first.length > 0 && first[0] === first[0]?.toUpperCase();
95
+
96
+ if (linkType === "func" || linkType === "const") {
97
+ return {
98
+ type: linkType,
99
+ namespace: first,
100
+ target: second,
101
+ member: undefined,
102
+ };
103
+ }
104
+
105
+ if (isNamespace) {
106
+ return {
107
+ type: linkType,
108
+ namespace: first,
109
+ target: second,
110
+ member: undefined,
111
+ };
112
+ }
113
+
114
+ return {
115
+ type: linkType,
116
+ namespace: undefined,
117
+ target: first,
118
+ member: second,
119
+ };
120
+ }
121
+
122
+ return {
123
+ type: linkType,
124
+ namespace: parts[0],
125
+ target: parts[1] ?? "",
126
+ member: parts.slice(2).join(".") || undefined,
127
+ };
128
+ }
129
+
130
+ function formatGirLinkForTsDoc(link: GirLink): string {
131
+ let displayText: string;
132
+ if (link.member) {
133
+ displayText = `${link.target}.${link.member}`;
134
+ } else {
135
+ displayText = link.target;
136
+ }
137
+
138
+ const linkTarget = link.namespace ? `${link.namespace}.${displayText}` : displayText;
139
+
140
+ switch (link.type) {
141
+ case "class":
142
+ case "iface":
143
+ case "struct":
144
+ case "enum":
145
+ case "flags":
146
+ case "error":
147
+ case "callback":
148
+ case "type":
149
+ return `{@link ${linkTarget}}`;
150
+
151
+ case "method":
152
+ case "vfunc":
153
+ case "func":
154
+ case "ctor":
155
+ return `{@link ${linkTarget}}`;
156
+
157
+ case "property":
158
+ return `{@link ${linkTarget}}`;
159
+
160
+ case "signal":
161
+ return `{@link ${linkTarget}}`;
162
+
163
+ case "const":
164
+ return `{@link ${linkTarget}}`;
165
+
166
+ case "id":
167
+ return `\`${link.target}\``;
168
+
169
+ default:
170
+ return `\`${displayText}\``;
171
+ }
172
+ }
173
+
174
+ function convertGirLinks(text: string): string {
175
+ return text.replace(GIR_LINK_PATTERN, (_, type: string, reference: string) => {
176
+ const link = parseGirLink(type, reference);
177
+ if (!link) {
178
+ return `\`${reference}\``;
179
+ }
180
+ return formatGirLinkForTsDoc(link);
181
+ });
182
+ }
183
+
184
+ const NAMESPACE_TO_DOCS_PATH: Record<string, string> = {
185
+ Gtk: "gtk4",
186
+ Gdk: "gdk4",
187
+ Gsk: "gsk4",
188
+ Adw: "adw1",
189
+ GLib: "glib",
190
+ GObject: "gobject",
191
+ Gio: "gio",
192
+ Pango: "Pango",
193
+ PangoCairo: "PangoCairo",
194
+ GdkPixbuf: "gdk-pixbuf",
195
+ Cairo: "cairo",
196
+ };
197
+
198
+ function getDocsBaseUrl(namespace: string | undefined): string {
199
+ if (!namespace) {
200
+ return "https://docs.gtk.org/gtk4";
201
+ }
202
+ const docsPath = NAMESPACE_TO_DOCS_PATH[namespace];
203
+ if (docsPath) {
204
+ return `https://docs.gtk.org/${docsPath}`;
205
+ }
206
+ return `https://docs.gtk.org/${namespace.toLowerCase()}`;
207
+ }
208
+
209
+ function convertHtmlImageElements(text: string, baseUrl: string): string {
210
+ let result = text;
211
+
212
+ result = result.replace(/<picture[^>]*>([\s\S]*?)<\/picture>/gi, (_, pictureContent: string) => {
213
+ const imgMatch = /<img[^>]*alt="([^"]*)"[^>]*src="([^"]*)"[^>]*\/?>/i.exec(pictureContent);
214
+ if (!imgMatch) {
215
+ const imgMatch2 = /<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/i.exec(pictureContent);
216
+ if (imgMatch2) {
217
+ const src = imgMatch2[1];
218
+ const alt = imgMatch2[2];
219
+ return `![${alt}](${baseUrl}/${src})`;
220
+ }
221
+ return "";
222
+ }
223
+ const alt = imgMatch[1];
224
+ const src = imgMatch[2];
225
+ return `![${alt}](${baseUrl}/${src})`;
226
+ });
227
+
228
+ result = result.replace(/<img[^>]*alt="([^"]*)"[^>]*src="([^"]*)"[^>]*\/?>/gi, (_, alt: string, src: string) => {
229
+ if (src.startsWith("http://") || src.startsWith("https://")) {
230
+ return `![${alt}](${src})`;
231
+ }
232
+ return `![${alt}](${baseUrl}/${src})`;
233
+ });
234
+
235
+ result = result.replace(/<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, (_, src: string, alt: string) => {
236
+ if (src.startsWith("http://") || src.startsWith("https://")) {
237
+ return `![${alt}](${src})`;
238
+ }
239
+ return `![${alt}](${baseUrl}/${src})`;
240
+ });
241
+
242
+ result = result.replace(/<img[^>]*src="([^"]*)"[^>]*\/?>/gi, (_, src: string) => {
243
+ if (src.startsWith("http://") || src.startsWith("https://")) {
244
+ return `![](${src})`;
245
+ }
246
+ return `![](${baseUrl}/${src})`;
247
+ });
248
+
249
+ result = result.replace(/<source[^>]*\/?>/gi, "");
250
+
251
+ return result;
252
+ }
253
+
254
+ function convertMarkdownImageUrls(text: string, baseUrl: string): string {
255
+ return text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt: string, src: string) => {
256
+ if (src.startsWith("http://") || src.startsWith("https://")) {
257
+ return match;
258
+ }
259
+ return `![${alt}](${baseUrl}/${src})`;
260
+ });
261
+ }
262
+
263
+ function convertKbdElements(text: string): string {
264
+ return text.replace(/<kbd>([^<]*)<\/kbd>/gi, "`$1`");
265
+ }
266
+
267
+ function stripHtmlLinks(text: string): string {
268
+ return text.replace(/\[([^\]]+)\]\([^)]+\.html[^)]*\)/gi, "$1");
269
+ }
270
+
271
+ function convertAtAnnotations(text: string): string {
272
+ return text.replace(/(?<!\{)@([a-zA-Z_][a-zA-Z0-9_]*)\b(?!\s*\{)/g, "`$1`");
273
+ }
274
+
275
+ function escapeXmlStyleTags(text: string): string {
276
+ return text.replace(/<(\/?)(child|object|property|signal|template|style|item|attribute)>/gi, "`<$1$2>`");
277
+ }
278
+
279
+ function cleanupWhitespace(text: string): string {
280
+ let result = text.replace(/\n{3,}/g, "\n\n");
281
+ result = result.replace(/[ \t]+$/gm, "");
282
+ return result.trim();
283
+ }
284
+
285
+ export interface SanitizeDocOptions {
286
+ escapeXmlTags?: boolean;
287
+ namespace?: string;
288
+ }
289
+
290
+ export function sanitizeDoc(doc: string, options: SanitizeDocOptions = {}): string {
291
+ let result = doc;
292
+
293
+ const baseUrl = getDocsBaseUrl(options.namespace);
294
+ result = convertHtmlImageElements(result, baseUrl);
295
+ result = convertMarkdownImageUrls(result, baseUrl);
296
+ result = convertKbdElements(result);
297
+ result = stripHtmlLinks(result);
298
+ result = convertGirLinks(result);
299
+ result = convertAtAnnotations(result);
300
+
301
+ if (options.escapeXmlTags) {
302
+ result = escapeXmlStyleTags(result);
303
+ }
304
+
305
+ result = cleanupWhitespace(result);
306
+
307
+ return result;
308
+ }
309
+
310
+ export function formatDoc(doc: string | undefined, indent: string = "", options: SanitizeDocOptions = {}): string {
311
+ if (!doc) {
312
+ return "";
313
+ }
314
+
315
+ const sanitized = sanitizeDoc(doc, options);
316
+ if (!sanitized) {
317
+ return "";
318
+ }
319
+
320
+ const lines = sanitized.split("\n").map((line) => line.trim());
321
+ const firstLine = lines[0] ?? "";
322
+
323
+ if (lines.length === 1 && firstLine.length < 80) {
324
+ return `${indent}/** ${firstLine} */\n`;
325
+ }
326
+
327
+ const formattedLines = lines.map((line) => `${indent} * ${line}`);
328
+ return `${indent}/**\n${formattedLines.join("\n")}\n${indent} */\n`;
329
+ }
330
+
331
+ interface DocParameter {
332
+ name: string;
333
+ doc: string | undefined;
334
+ }
335
+
336
+ export function formatMethodDoc(
337
+ doc: string | undefined,
338
+ params: DocParameter[],
339
+ indent: string = " ",
340
+ options: SanitizeDocOptions = {},
341
+ ): string {
342
+ const sanitizedDoc = doc ? sanitizeDoc(doc, options) : undefined;
343
+
344
+ const hasDocumentation = sanitizedDoc || params.some((p) => p.doc);
345
+ if (!hasDocumentation) {
346
+ return "";
347
+ }
348
+
349
+ const lines: string[] = [];
350
+
351
+ if (sanitizedDoc) {
352
+ for (const line of sanitizedDoc.split("\n")) {
353
+ lines.push(` * ${line.trim()}`);
354
+ }
355
+ }
356
+
357
+ for (const param of params) {
358
+ if (param.doc && param.name && param.name !== "..." && param.name !== "") {
359
+ const sanitizedParamDoc = sanitizeDoc(param.doc, options);
360
+ const paramDocFirstLine = sanitizedParamDoc.split("\n")[0]?.trim() ?? "";
361
+ lines.push(` * @param ${param.name} - ${paramDocFirstLine}`);
362
+ }
363
+ }
364
+
365
+ if (lines.length === 0) {
366
+ return "";
367
+ }
368
+
369
+ return `${indent}/**\n${indent}${lines.join(`\n${indent}`)}\n${indent} */\n`;
370
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
+ export * from "./doc-sanitizer.js";
1
2
  export * from "./parser.js";
2
3
  export * from "./types.js";
package/src/types.ts CHANGED
@@ -424,6 +424,8 @@ export type RegisteredType = {
424
424
  namespace: string;
425
425
  transformedName: string;
426
426
  glibTypeName?: string;
427
+ sharedLibrary?: string;
428
+ glibGetType?: string;
427
429
  };
428
430
 
429
431
  const CLASS_RENAMES = new Map<string, string>([["Error", "GError"]]);
@@ -496,8 +498,16 @@ export class TypeRegistry {
496
498
  * @param namespace - The namespace containing the record
497
499
  * @param name - The record name
498
500
  * @param glibTypeName - Optional GLib type name for boxed type handling
501
+ * @param sharedLibrary - The shared library containing this record's type
502
+ * @param glibGetType - The GLib get_type function name
499
503
  */
500
- registerRecord(namespace: string, name: string, glibTypeName?: string): void {
504
+ registerRecord(
505
+ namespace: string,
506
+ name: string,
507
+ glibTypeName?: string,
508
+ sharedLibrary?: string,
509
+ glibGetType?: string,
510
+ ): void {
501
511
  const transformedName = normalizeTypeName(name, namespace);
502
512
  this.types.set(`${namespace}.${name}`, {
503
513
  kind: "record",
@@ -505,6 +515,8 @@ export class TypeRegistry {
505
515
  namespace,
506
516
  transformedName,
507
517
  glibTypeName,
518
+ sharedLibrary,
519
+ glibGetType,
508
520
  });
509
521
  }
510
522
 
@@ -580,7 +592,13 @@ export class TypeRegistry {
580
592
  }
581
593
  for (const record of ns.records) {
582
594
  if (record.glibTypeName && !record.disguised) {
583
- registry.registerRecord(ns.name, record.name, record.glibTypeName);
595
+ registry.registerRecord(
596
+ ns.name,
597
+ record.name,
598
+ record.glibTypeName,
599
+ ns.sharedLibrary,
600
+ record.glibGetType,
601
+ );
584
602
  }
585
603
  }
586
604
  for (const callback of ns.callbacks) {
@@ -944,6 +962,8 @@ export class TypeMapper {
944
962
  type: "boxed",
945
963
  borrowed: isReturn,
946
964
  innerType: registered.glibTypeName ?? registered.transformedName,
965
+ lib: registered.sharedLibrary,
966
+ getTypeFn: registered.glibGetType,
947
967
  },
948
968
  externalType,
949
969
  kind: registered.kind,
@@ -1033,6 +1053,8 @@ export class TypeMapper {
1033
1053
  type: "boxed",
1034
1054
  borrowed: isReturn,
1035
1055
  innerType: registered.glibTypeName ?? registered.transformedName,
1056
+ lib: registered.sharedLibrary,
1057
+ getTypeFn: registered.glibGetType,
1036
1058
  },
1037
1059
  externalType: isExternal ? externalType : undefined,
1038
1060
  };
@@ -1091,6 +1113,8 @@ export class TypeMapper {
1091
1113
  type: "boxed",
1092
1114
  borrowed: isReturn,
1093
1115
  innerType: registered.glibTypeName ?? registered.transformedName,
1116
+ lib: registered.sharedLibrary,
1117
+ getTypeFn: registered.glibGetType,
1094
1118
  },
1095
1119
  externalType,
1096
1120
  kind: registered.kind,
@@ -1192,14 +1216,14 @@ export class TypeMapper {
1192
1216
 
1193
1217
  if (param.type.name === "Gtk.DrawingAreaDrawFunc" || param.type.name === "DrawingAreaDrawFunc") {
1194
1218
  this.onExternalTypeUsed?.({
1195
- namespace: "Cairo",
1219
+ namespace: "cairo",
1196
1220
  name: "Context",
1197
1221
  transformedName: "Context",
1198
1222
  kind: "record",
1199
1223
  });
1200
1224
  this.onSameNamespaceClassUsed?.("DrawingArea", "DrawingArea");
1201
1225
  return {
1202
- ts: "(self: DrawingArea, cr: Cairo.Context, width: number, height: number) => void",
1226
+ ts: "(self: DrawingArea, cr: cairo.Context, width: number, height: number) => void",
1203
1227
  ffi: {
1204
1228
  type: "callback",
1205
1229
  trampoline: "drawFunc",