@ts-for-gir/lib 4.0.0-rc.6 → 4.0.0-rc.7
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 +5 -5
- package/src/gir/function.ts +1 -3
- package/src/gir/introspected-classes.ts +57 -4
- package/src/gir/promisify.ts +5 -0
- package/src/gir/property.ts +17 -2
- package/src/utils/conflicts.ts +7 -2
- package/src/utils/gir-defaults.ts +66 -0
- package/src/utils/index.ts +1 -0
- package/src/validators/class.ts +16 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ts-for-gir/lib",
|
|
3
|
-
"version": "4.0.0-rc.
|
|
3
|
+
"version": "4.0.0-rc.7",
|
|
4
4
|
"description": "Typescript .d.ts generator from GIR for gjs",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"module": "src/index.ts",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"type definitions"
|
|
42
42
|
],
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@ts-for-gir/tsconfig": "^4.0.0-rc.
|
|
44
|
+
"@ts-for-gir/tsconfig": "^4.0.0-rc.7",
|
|
45
45
|
"@types/ejs": "^3.1.5",
|
|
46
46
|
"@types/lodash": "^4.17.24",
|
|
47
47
|
"@types/node": "^25.6.0",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"typescript": "^6.0.3"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@gi.ts/parser": "^4.0.0-rc.
|
|
53
|
-
"@ts-for-gir/reporter": "^4.0.0-rc.
|
|
54
|
-
"@ts-for-gir/templates": "^4.0.0-rc.
|
|
52
|
+
"@gi.ts/parser": "^4.0.0-rc.7",
|
|
53
|
+
"@ts-for-gir/reporter": "^4.0.0-rc.7",
|
|
54
|
+
"@ts-for-gir/templates": "^4.0.0-rc.7",
|
|
55
55
|
"colorette": "^2.0.20",
|
|
56
56
|
"ejs": "^5.0.2",
|
|
57
57
|
"glob": "^13.0.6",
|
package/src/gir/function.ts
CHANGED
|
@@ -248,9 +248,7 @@ export class IntrospectedFunction extends IntrospectedNamespaceMember {
|
|
|
248
248
|
const { type, isOptional } = p;
|
|
249
249
|
|
|
250
250
|
if (allowOptions) {
|
|
251
|
-
if (
|
|
252
|
-
params.push(p.copy({ isOptional: true }));
|
|
253
|
-
} else if (!isOptional) {
|
|
251
|
+
if (!isOptional) {
|
|
254
252
|
params.push(p);
|
|
255
253
|
return { allowOptions: false, params };
|
|
256
254
|
} else {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
Generic,
|
|
6
6
|
GenericType,
|
|
7
7
|
GenerifiedTypeIdentifier,
|
|
8
|
+
NullableType,
|
|
8
9
|
type TypeExpression,
|
|
9
10
|
TypeIdentifier,
|
|
10
11
|
UnknownType,
|
|
@@ -47,6 +48,40 @@ import { IntrospectedSignal } from "./signal.ts";
|
|
|
47
48
|
|
|
48
49
|
const log = new ConsoleReporter(true, "gir/introspected-classes", true);
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Walks the implementing class's `extends` chain (skipping the prerequisite class
|
|
53
|
+
* itself) and reports whether any ancestor *shadows* a member by declaring its
|
|
54
|
+
* own member of the given name. A shadow indicates the parent chain has
|
|
55
|
+
* overridden the prerequisite's member with a possibly incompatible type, which
|
|
56
|
+
* would otherwise leave the implementing class unable to satisfy the
|
|
57
|
+
* interface's contract.
|
|
58
|
+
*/
|
|
59
|
+
function hasExtendsShadowOf(cls: IntrospectedBaseClass, prerequisite: IntrospectedBaseClass, name: string): boolean {
|
|
60
|
+
let current = cls.resolveParents().extends();
|
|
61
|
+
while (current) {
|
|
62
|
+
const node = current.node;
|
|
63
|
+
if (node !== prerequisite) {
|
|
64
|
+
const hasOwn = [...node.props, ...node.fields, ...node.members].some((m) => m.name === name);
|
|
65
|
+
if (hasOwn) return true;
|
|
66
|
+
}
|
|
67
|
+
current = current.extends();
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveNullableProperties(cls: IntrospectedBaseClass): void {
|
|
73
|
+
for (const prop of cls.props) {
|
|
74
|
+
if (prop.type instanceof NullableType) continue;
|
|
75
|
+
|
|
76
|
+
const getterName = prop.getter ?? `get_${prop.name}`;
|
|
77
|
+
const getter = cls.members.find((m) => m.name === getterName && !(m instanceof IntrospectedStaticClassFunction));
|
|
78
|
+
|
|
79
|
+
if (getter instanceof IntrospectedClassFunction && getter.return() instanceof NullableType) {
|
|
80
|
+
prop.type = new NullableType(prop.type);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
50
85
|
/**
|
|
51
86
|
* Represents a signal with metadata
|
|
52
87
|
*/
|
|
@@ -75,6 +110,8 @@ export class IntrospectedClassFunction<
|
|
|
75
110
|
returnTypeDoc?: string | null;
|
|
76
111
|
/** If this function was generated from a signal, stores the signal name. */
|
|
77
112
|
signalOrigin?: string;
|
|
113
|
+
/** GIR glib:finish-func attribute: name of the function that finishes this async operation. */
|
|
114
|
+
finishFuncName?: string;
|
|
78
115
|
|
|
79
116
|
generics: Generic[] = [];
|
|
80
117
|
|
|
@@ -143,6 +180,7 @@ export class IntrospectedClassFunction<
|
|
|
143
180
|
|
|
144
181
|
fn.generics = [...this.generics];
|
|
145
182
|
fn.returnTypeDoc = this.returnTypeDoc;
|
|
183
|
+
fn.finishFuncName = this.finishFuncName;
|
|
146
184
|
|
|
147
185
|
if (interfaceParent) {
|
|
148
186
|
fn.interfaceParent = interfaceParent;
|
|
@@ -177,6 +215,10 @@ export class IntrospectedClassFunction<
|
|
|
177
215
|
// Convert the function to a class function
|
|
178
216
|
const { raw_name: name, output_parameters, parameters, return_type, doc, isIntrospectable } = fn;
|
|
179
217
|
|
|
218
|
+
// A function with shadowed-by is superseded by the shadowing function (which uses `shadows`)
|
|
219
|
+
// and takes the original name. Do not emit the shadowed function to avoid duplicate declarations.
|
|
220
|
+
const isShadowedBy = element.$["shadowed-by"] != null;
|
|
221
|
+
|
|
180
222
|
const classFn = new IntrospectedClassFunction({
|
|
181
223
|
parent,
|
|
182
224
|
name,
|
|
@@ -184,11 +226,12 @@ export class IntrospectedClassFunction<
|
|
|
184
226
|
parameters,
|
|
185
227
|
return_type,
|
|
186
228
|
doc,
|
|
187
|
-
isIntrospectable,
|
|
229
|
+
isIntrospectable: isIntrospectable && !isShadowedBy,
|
|
188
230
|
});
|
|
189
231
|
|
|
190
232
|
classFn.returnTypeDoc = fn.returnTypeDoc;
|
|
191
233
|
classFn.generics = [...fn.generics];
|
|
234
|
+
classFn.finishFuncName = element.$["glib:finish-func"];
|
|
192
235
|
|
|
193
236
|
return classFn;
|
|
194
237
|
}
|
|
@@ -396,6 +439,8 @@ export class IntrospectedStaticClassFunction extends IntrospectedClassFunction {
|
|
|
396
439
|
// Convert the function to a static class function
|
|
397
440
|
const { raw_name: name, output_parameters, parameters, return_type, doc, isIntrospectable } = fn;
|
|
398
441
|
|
|
442
|
+
const isShadowedBy = m.$["shadowed-by"] != null;
|
|
443
|
+
|
|
399
444
|
return new IntrospectedStaticClassFunction({
|
|
400
445
|
parent,
|
|
401
446
|
name,
|
|
@@ -403,7 +448,7 @@ export class IntrospectedStaticClassFunction extends IntrospectedClassFunction {
|
|
|
403
448
|
parameters,
|
|
404
449
|
return_type,
|
|
405
450
|
doc,
|
|
406
|
-
isIntrospectable,
|
|
451
|
+
isIntrospectable: isIntrospectable && !isShadowedBy,
|
|
407
452
|
});
|
|
408
453
|
}
|
|
409
454
|
}
|
|
@@ -853,13 +898,19 @@ export class IntrospectedClass extends IntrospectedBaseClass {
|
|
|
853
898
|
});
|
|
854
899
|
}
|
|
855
900
|
|
|
856
|
-
//
|
|
857
|
-
//
|
|
901
|
+
// An interface's <prerequisite> of class type is always satisfied by the
|
|
902
|
+
// implementing class's actual parent chain — those members are already
|
|
903
|
+
// inherited via TS class inheritance, so we don't re-emit them. The one
|
|
904
|
+
// case we still need to handle: when the parent chain has a same-named
|
|
905
|
+
// member with an incompatible signature/type, we re-emit the interface's
|
|
906
|
+
// version so `filterConflicts` / `filterFunctionConflict` can broaden or
|
|
907
|
+
// override it to satisfy both `extends` and `implements` simultaneously.
|
|
858
908
|
for (const implemented of resolution.implements()) {
|
|
859
909
|
const extended = implemented.extends();
|
|
860
910
|
if (extended?.node instanceof IntrospectedClass) {
|
|
861
911
|
for (const item of getItems(extended.node)) {
|
|
862
912
|
if (items.has(item.name) || !validate(item)) continue;
|
|
913
|
+
if (!hasExtendsShadowOf(this, extended.node, item.name)) continue;
|
|
863
914
|
items.set(item.name, item);
|
|
864
915
|
}
|
|
865
916
|
}
|
|
@@ -1036,6 +1087,7 @@ export class IntrospectedClass extends IntrospectedBaseClass {
|
|
|
1036
1087
|
IntrospectedClass.parseBasicProperties(element, clazz, ns, options);
|
|
1037
1088
|
IntrospectedClass.parseResolveNames(element, clazz, ns, name);
|
|
1038
1089
|
IntrospectedClass.parseInheritanceAndMembers(element, clazz, ns, options);
|
|
1090
|
+
resolveNullableProperties(clazz);
|
|
1039
1091
|
|
|
1040
1092
|
return clazz;
|
|
1041
1093
|
}
|
|
@@ -1342,6 +1394,7 @@ export class IntrospectedInterface extends IntrospectedBaseClass {
|
|
|
1342
1394
|
IntrospectedInterface.parseInterfaceBasicProperties(element, iface, namespace, options);
|
|
1343
1395
|
IntrospectedInterface.parseInterfaceResolveNames(element, iface, namespace, name);
|
|
1344
1396
|
IntrospectedInterface.parseInterfaceMembers(element, iface, namespace, options);
|
|
1397
|
+
resolveNullableProperties(iface);
|
|
1345
1398
|
|
|
1346
1399
|
return iface;
|
|
1347
1400
|
}
|
package/src/gir/promisify.ts
CHANGED
|
@@ -62,6 +62,11 @@ function findFinishMethodInClass(cls: IntrospectedBaseClass, node: IntrospectedC
|
|
|
62
62
|
? [...cls.constructors, ...cls.members.filter((m) => m instanceof IntrospectedStaticClassFunction)]
|
|
63
63
|
: [...cls.members.filter((m) => !(m instanceof IntrospectedStaticClassFunction))];
|
|
64
64
|
|
|
65
|
+
// Prefer the GIR-specified finish function name over name heuristics
|
|
66
|
+
if (node.finishFuncName) {
|
|
67
|
+
return members.find((m) => m.name === node.finishFuncName);
|
|
68
|
+
}
|
|
69
|
+
|
|
65
70
|
return members.find(
|
|
66
71
|
(m) => m.name === `${node.name.replace(/_async$/, "")}_finish` || m.name === `${node.name}_finish`,
|
|
67
72
|
);
|
package/src/gir/property.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FormatGenerator } from "../generators/generator.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { NullableType, type TypeExpression } from "../gir.ts";
|
|
3
3
|
import type { GirFieldElement, GirPropertyElement } from "../index.ts";
|
|
4
4
|
import type { OptionsLoad } from "../types/index.ts";
|
|
5
5
|
import type { Options } from "../types/introspected.ts";
|
|
@@ -98,6 +98,10 @@ export class IntrospectedProperty extends IntrospectedBase<IntrospectedEnum | In
|
|
|
98
98
|
readonly writable: boolean = false;
|
|
99
99
|
readonly readable: boolean = true;
|
|
100
100
|
readonly constructOnly: boolean;
|
|
101
|
+
/** GIR default-value attribute: the default value of the property as a string. */
|
|
102
|
+
defaultValue?: string;
|
|
103
|
+
/** GIR getter attribute: name of the getter method for this property (used for nullable inference). */
|
|
104
|
+
getter?: string;
|
|
101
105
|
|
|
102
106
|
get namespace() {
|
|
103
107
|
return this.parent.namespace;
|
|
@@ -110,7 +114,7 @@ export class IntrospectedProperty extends IntrospectedBase<IntrospectedEnum | In
|
|
|
110
114
|
}): IntrospectedProperty {
|
|
111
115
|
const { name, writable, readable, type, constructOnly, parent } = this;
|
|
112
116
|
|
|
113
|
-
|
|
117
|
+
const prop = new IntrospectedProperty({
|
|
114
118
|
name: options?.name ?? name,
|
|
115
119
|
writable,
|
|
116
120
|
readable,
|
|
@@ -118,6 +122,9 @@ export class IntrospectedProperty extends IntrospectedBase<IntrospectedEnum | In
|
|
|
118
122
|
constructOnly,
|
|
119
123
|
parent: options?.parent ?? parent,
|
|
120
124
|
})._copyBaseProperties(this);
|
|
125
|
+
prop.defaultValue = this.defaultValue;
|
|
126
|
+
prop.getter = this.getter;
|
|
127
|
+
return prop;
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
accept(visitor: GirVisitor): IntrospectedProperty {
|
|
@@ -198,6 +205,14 @@ export class IntrospectedProperty extends IntrospectedBase<IntrospectedEnum | In
|
|
|
198
205
|
property.metadata = parseMetadata(element);
|
|
199
206
|
}
|
|
200
207
|
|
|
208
|
+
property.defaultValue = element.$["default-value"];
|
|
209
|
+
|
|
210
|
+
property.getter = element.$.getter;
|
|
211
|
+
|
|
212
|
+
if (element.$.nullable === "1" || element.$["allow-none"] === "1") {
|
|
213
|
+
property.type = new NullableType(property.type);
|
|
214
|
+
}
|
|
215
|
+
|
|
201
216
|
return property;
|
|
202
217
|
}
|
|
203
218
|
}
|
package/src/utils/conflicts.ts
CHANGED
|
@@ -261,8 +261,13 @@ function checkFunctionConflicts<T extends IntrospectedClassFunction | Introspect
|
|
|
261
261
|
});
|
|
262
262
|
});
|
|
263
263
|
|
|
264
|
-
//
|
|
265
|
-
|
|
264
|
+
// Static methods can coexist with instance fields/properties of the same name
|
|
265
|
+
// in TypeScript (e.g. `static map(...)` alongside `map: T[]`), so skip the check
|
|
266
|
+
// for them. The conflict only applies to instance methods.
|
|
267
|
+
const hasFieldConflicts =
|
|
268
|
+
functionElement instanceof IntrospectedStaticClassFunction
|
|
269
|
+
? false
|
|
270
|
+
: checkFieldPropertyConflicts(base, functionElement.name);
|
|
266
271
|
|
|
267
272
|
// Check GObject reserved methods
|
|
268
273
|
const hasGObjectConflicts = checkGObjectConflicts(base, functionElement.name);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { GirModule } from "../gir-module.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a single C enum/bitfield constant to its GJS-qualified path by searching
|
|
5
|
+
* the module's own enum_constants map and then all direct and transitive dependencies.
|
|
6
|
+
* Returns [namespaceName, enumTypeName, memberName] or null if not found.
|
|
7
|
+
*
|
|
8
|
+
* Called during the generation phase (after all modules have been parsed), so all
|
|
9
|
+
* dependency enum_constants maps are fully populated. Each GirModule.enum_constants
|
|
10
|
+
* is an O(1) Map; the total cost per call is O(deps) — negligible given the small
|
|
11
|
+
* fraction of properties that carry a default-value attribute.
|
|
12
|
+
*/
|
|
13
|
+
function resolveCEnumConstant(cIdentifier: string, ns: GirModule): readonly [string, string, string] | null {
|
|
14
|
+
// Check own namespace first
|
|
15
|
+
const own = ns.enum_constants.get(cIdentifier);
|
|
16
|
+
if (own) return [ns.namespace, own[0], own[1]] as const;
|
|
17
|
+
|
|
18
|
+
// Check all direct and transitive dependencies
|
|
19
|
+
for (const dep of ns.allDependencies) {
|
|
20
|
+
const depModule = ns.getInstalledImport(dep.namespace);
|
|
21
|
+
if (!depModule) continue;
|
|
22
|
+
const entry = depModule.enum_constants.get(cIdentifier);
|
|
23
|
+
if (entry) return [depModule.namespace, entry[0], entry[1]] as const;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function convertSingleCValue(value: string, ns: GirModule): string {
|
|
30
|
+
const trimmed = value.trim();
|
|
31
|
+
|
|
32
|
+
if (trimmed === "NULL") return "null";
|
|
33
|
+
if (trimmed === "TRUE") return "true";
|
|
34
|
+
if (trimmed === "FALSE") return "false";
|
|
35
|
+
|
|
36
|
+
// Normalize C floats: "0.000000" → "0", "1.500000" → "1.5"
|
|
37
|
+
if (/^-?\d+\.\d+$/.test(trimmed)) {
|
|
38
|
+
const n = parseFloat(trimmed);
|
|
39
|
+
if (!Number.isNaN(n)) return String(n);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Resolve C enum/bitfield constant (own namespace + all dependencies)
|
|
43
|
+
// e.g. "GTK_ALIGN_FILL" → "Gtk.Align.FILL"
|
|
44
|
+
// e.g. "GDK_NO_MODIFIER_MASK" → "Gdk.ModifierType.NO_MODIFIER_MASK"
|
|
45
|
+
// e.g. "PANGO_ALIGN_LEFT" → "Pango.Alignment.LEFT"
|
|
46
|
+
const entry = resolveCEnumConstant(trimmed, ns);
|
|
47
|
+
if (entry) return `${entry[0]}.${entry[1]}.${entry[2]}`;
|
|
48
|
+
|
|
49
|
+
return trimmed;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Converts a raw C default-value string from GIR XML to its JavaScript equivalent.
|
|
54
|
+
* Handles NULL/TRUE/FALSE, C float literals, C enum/bitfield constants (including
|
|
55
|
+
* cross-namespace lookups), and bitmask combinations (A | B | C).
|
|
56
|
+
*/
|
|
57
|
+
export function convertCDefaultValue(rawValue: string, ns: GirModule): string {
|
|
58
|
+
// Handle bitmask combinations: "A | B | C"
|
|
59
|
+
if (rawValue.includes("|")) {
|
|
60
|
+
return rawValue
|
|
61
|
+
.split("|")
|
|
62
|
+
.map((part) => convertSingleCValue(part, ns))
|
|
63
|
+
.join(" | ");
|
|
64
|
+
}
|
|
65
|
+
return convertSingleCValue(rawValue, ns);
|
|
66
|
+
}
|
package/src/utils/index.ts
CHANGED
package/src/validators/class.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { IntrospectedError } from "../gir/error.ts";
|
|
|
3
3
|
import type { IntrospectedBaseClass, IntrospectedClass, IntrospectedInterface } from "../gir/introspected-classes.ts";
|
|
4
4
|
import { IntrospectedClassFunction, IntrospectedStaticClassFunction } from "../gir/introspected-classes.ts";
|
|
5
5
|
import { IntrospectedRecord } from "../gir/record.ts";
|
|
6
|
-
import { AnyType, NativeType, TypeIdentifier } from "../gir.ts";
|
|
6
|
+
import { AnyType, ArrayType, NativeType, TypeIdentifier } from "../gir.ts";
|
|
7
7
|
import { resolveTypeIdentifier } from "../utils/type-resolution.ts";
|
|
8
8
|
import { GirVisitor } from "../visitor.ts";
|
|
9
9
|
|
|
@@ -125,9 +125,10 @@ const fixMissingParent = <T extends IntrospectedBaseClass>(node: T): T => {
|
|
|
125
125
|
};
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* fields
|
|
128
|
+
* Removes fields with types that GJS cannot directly expose on a struct instance.
|
|
129
|
+
* Error types and non-simple non-pointer struct fields are removed.
|
|
130
|
+
* Array fields (zero-terminated pointer arrays, T**) are always safe in GJS and are only
|
|
131
|
+
* rejected if the element type is private or disguised.
|
|
131
132
|
*
|
|
132
133
|
* @param node
|
|
133
134
|
*/
|
|
@@ -135,6 +136,17 @@ const removeComplexFields = <T extends IntrospectedBaseClass>(node: T): T => {
|
|
|
135
136
|
const { namespace } = node;
|
|
136
137
|
|
|
137
138
|
node.fields = node.fields.filter((f) => {
|
|
139
|
+
// Array fields (T**) are marshalled by GJS for any GBoxed element type.
|
|
140
|
+
// Only reject arrays of private/disguised element types.
|
|
141
|
+
if (f.type instanceof ArrayType) {
|
|
142
|
+
const elementType = f.type.deepUnwrap();
|
|
143
|
+
if (elementType instanceof TypeIdentifier) {
|
|
144
|
+
const classNode = resolveTypeIdentifier(namespace, elementType);
|
|
145
|
+
return !classNode?.isPrivate;
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
138
150
|
const type = f.type.deepUnwrap();
|
|
139
151
|
|
|
140
152
|
if (type instanceof NativeType) {
|