@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 +1 -1
- package/src/doc-sanitizer.ts +370 -0
- package/src/index.ts +1 -0
- package/src/types.ts +28 -4
package/package.json
CHANGED
|
@@ -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 ``;
|
|
220
|
+
}
|
|
221
|
+
return "";
|
|
222
|
+
}
|
|
223
|
+
const alt = imgMatch[1];
|
|
224
|
+
const src = imgMatch[2];
|
|
225
|
+
return ``;
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
result = result.replace(/<img[^>]*alt="([^"]*)"[^>]*src="([^"]*)"[^>]*\/?>/gi, (_, alt: string, src: string) => {
|
|
229
|
+
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
230
|
+
return ``;
|
|
231
|
+
}
|
|
232
|
+
return ``;
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
result = result.replace(/<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, (_, src: string, alt: string) => {
|
|
236
|
+
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
237
|
+
return ``;
|
|
238
|
+
}
|
|
239
|
+
return ``;
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
result = result.replace(/<img[^>]*src="([^"]*)"[^>]*\/?>/gi, (_, src: string) => {
|
|
243
|
+
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
244
|
+
return ``;
|
|
245
|
+
}
|
|
246
|
+
return ``;
|
|
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 ``;
|
|
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
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(
|
|
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(
|
|
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: "
|
|
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:
|
|
1226
|
+
ts: "(self: DrawingArea, cr: cairo.Context, width: number, height: number) => void",
|
|
1203
1227
|
ffi: {
|
|
1204
1228
|
type: "callback",
|
|
1205
1229
|
trampoline: "drawFunc",
|