@ts-for-gir/lib 4.0.0-rc.1 → 4.0.0-rc.12
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 +8 -8
- package/src/constants.ts +12 -5
- package/src/dependency-manager.ts +24 -6
- package/src/gir/function.ts +7 -6
- package/src/gir/introspected-classes.ts +58 -4
- package/src/gir/promisify.ts +5 -0
- package/src/gir/property.ts +17 -2
- package/src/gir/registry.ts +17 -13
- package/src/gir/signal.ts +2 -1
- package/src/gir.ts +51 -41
- package/src/types/options-generation.ts +23 -0
- package/src/types/user-config.ts +30 -0
- package/src/utils/conflicts.ts +7 -2
- package/src/utils/generation.ts +5 -3
- package/src/utils/gir-defaults.ts +66 -0
- package/src/utils/gir-parsing.ts +6 -4
- package/src/utils/index.ts +1 -0
- package/src/utils/path.ts +12 -4
- package/src/utils/types.ts +44 -8
- package/src/validators/class.ts +16 -4
- package/src/validators/function-parameters.ts +0 -52
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.12",
|
|
4
4
|
"description": "Typescript .d.ts generator from GIR for gjs",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"module": "src/index.ts",
|
|
@@ -41,19 +41,19 @@
|
|
|
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.12",
|
|
45
45
|
"@types/ejs": "^3.1.5",
|
|
46
46
|
"@types/lodash": "^4.17.24",
|
|
47
|
-
"@types/node": "^
|
|
47
|
+
"@types/node": "^25.6.0",
|
|
48
48
|
"rimraf": "^6.1.3",
|
|
49
|
-
"typescript": "^6.0.
|
|
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.12",
|
|
53
|
+
"@ts-for-gir/reporter": "^4.0.0-rc.12",
|
|
54
|
+
"@ts-for-gir/templates": "^4.0.0-rc.12",
|
|
55
55
|
"colorette": "^2.0.20",
|
|
56
|
-
"ejs": "^5.0.
|
|
56
|
+
"ejs": "^5.0.2",
|
|
57
57
|
"glob": "^13.0.6",
|
|
58
58
|
"lodash": "4.18.1"
|
|
59
59
|
}
|
package/src/constants.ts
CHANGED
|
@@ -18,11 +18,18 @@ function getPackageVersion(): string {
|
|
|
18
18
|
if (typeof __TS_FOR_GIR_VERSION__ !== "undefined") {
|
|
19
19
|
return __TS_FOR_GIR_VERSION__;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
// Dev mode reads the sibling package.json relative to this source file.
|
|
22
|
+
// Wrapped in try/catch so a bundle missing the __TS_FOR_GIR_VERSION__ define
|
|
23
|
+
// degrades to a fallback string instead of throwing at module load.
|
|
24
|
+
try {
|
|
25
|
+
const currentModulePath = fileURLToPath(import.meta.url);
|
|
26
|
+
const currentDir = dirname(currentModulePath);
|
|
27
|
+
const packageJsonPath = join(currentDir, "..", "package.json");
|
|
28
|
+
const content = readFileSync(packageJsonPath, "utf-8");
|
|
29
|
+
return (JSON.parse(content) as { version: string }).version;
|
|
30
|
+
} catch {
|
|
31
|
+
return "0.0.0-unknown";
|
|
32
|
+
}
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
export const APP_NAME = "ts-for-gir";
|
|
@@ -132,6 +132,15 @@ export class DependencyManager {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
createImportPath(packageName: string, namespace: string, version: string): string {
|
|
135
|
+
// In external-deps mode every dep import is resolved against an installed npm package
|
|
136
|
+
// (e.g. `@girs/glib-2.0`), regardless of `package` mode. User-supplied overrides win
|
|
137
|
+
// for namespaces with non-default scopes/versions (e.g. `Soup → @girs/soup-3.0`).
|
|
138
|
+
if (this.config.externalDeps) {
|
|
139
|
+
const override = this.config.externalPackages?.[namespace];
|
|
140
|
+
if (override) return override;
|
|
141
|
+
const importName = transformImportName(packageName);
|
|
142
|
+
return `${this.config.npmScope}/${importName}`;
|
|
143
|
+
}
|
|
135
144
|
if (!this.config.package) {
|
|
136
145
|
return `gi://${namespace}?version=${version}`;
|
|
137
146
|
}
|
|
@@ -147,13 +156,22 @@ export class DependencyManager {
|
|
|
147
156
|
}
|
|
148
157
|
|
|
149
158
|
createPackageJsonImport(importPath: string, libraryVersion?: LibraryVersion): string {
|
|
159
|
+
const pinnedVersion = libraryVersion ? `${libraryVersion.toString()}-${APP_VERSION}` : APP_VERSION;
|
|
160
|
+
const format = this.config.depVersionFormat ?? (this.config.workspace ? "workspace" : "exact");
|
|
150
161
|
let depVersion: string;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
162
|
+
switch (format) {
|
|
163
|
+
case "workspace":
|
|
164
|
+
depVersion = "workspace:^";
|
|
165
|
+
break;
|
|
166
|
+
case "caret":
|
|
167
|
+
depVersion = `^${pinnedVersion}`;
|
|
168
|
+
break;
|
|
169
|
+
case "any":
|
|
170
|
+
depVersion = "*";
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
depVersion = pinnedVersion;
|
|
174
|
+
break;
|
|
157
175
|
}
|
|
158
176
|
return `"${importPath}": "${depVersion}"`;
|
|
159
177
|
}
|
package/src/gir/function.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
ArrayType,
|
|
5
5
|
ClosureType,
|
|
6
6
|
type Generic,
|
|
7
|
-
|
|
7
|
+
makeNullable,
|
|
8
8
|
type TypeExpression,
|
|
9
9
|
TypeIdentifier,
|
|
10
10
|
UnknownType,
|
|
@@ -235,7 +235,10 @@ export class IntrospectedFunction extends IntrospectedNamespaceMember {
|
|
|
235
235
|
): IntrospectedFunctionParameter[] {
|
|
236
236
|
return parameters
|
|
237
237
|
.filter((_, i) => !length_params.includes(i) && !user_data_params.includes(i))
|
|
238
|
-
.filter((v) =>
|
|
238
|
+
.filter((v) => {
|
|
239
|
+
const unwrapped = v.type.unwrap();
|
|
240
|
+
return !(unwrapped instanceof TypeIdentifier && unwrapped.is("GLib", "DestroyNotify"));
|
|
241
|
+
});
|
|
239
242
|
}
|
|
240
243
|
|
|
241
244
|
private static processOptionalParameters(
|
|
@@ -248,9 +251,7 @@ export class IntrospectedFunction extends IntrospectedNamespaceMember {
|
|
|
248
251
|
const { type, isOptional } = p;
|
|
249
252
|
|
|
250
253
|
if (allowOptions) {
|
|
251
|
-
if (
|
|
252
|
-
params.push(p.copy({ isOptional: true }));
|
|
253
|
-
} else if (!isOptional) {
|
|
254
|
+
if (!isOptional) {
|
|
254
255
|
params.push(p);
|
|
255
256
|
return { allowOptions: false, params };
|
|
256
257
|
} else {
|
|
@@ -258,7 +259,7 @@ export class IntrospectedFunction extends IntrospectedNamespaceMember {
|
|
|
258
259
|
}
|
|
259
260
|
} else {
|
|
260
261
|
if (isOptional) {
|
|
261
|
-
params.push(p.copy({ type:
|
|
262
|
+
params.push(p.copy({ type: makeNullable(type), isOptional: false }));
|
|
262
263
|
} else {
|
|
263
264
|
params.push(p);
|
|
264
265
|
}
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
Generic,
|
|
6
6
|
GenericType,
|
|
7
7
|
GenerifiedTypeIdentifier,
|
|
8
|
+
makeNullable,
|
|
9
|
+
NullableType,
|
|
8
10
|
type TypeExpression,
|
|
9
11
|
TypeIdentifier,
|
|
10
12
|
UnknownType,
|
|
@@ -47,6 +49,40 @@ import { IntrospectedSignal } from "./signal.ts";
|
|
|
47
49
|
|
|
48
50
|
const log = new ConsoleReporter(true, "gir/introspected-classes", true);
|
|
49
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Walks the implementing class's `extends` chain (skipping the prerequisite class
|
|
54
|
+
* itself) and reports whether any ancestor *shadows* a member by declaring its
|
|
55
|
+
* own member of the given name. A shadow indicates the parent chain has
|
|
56
|
+
* overridden the prerequisite's member with a possibly incompatible type, which
|
|
57
|
+
* would otherwise leave the implementing class unable to satisfy the
|
|
58
|
+
* interface's contract.
|
|
59
|
+
*/
|
|
60
|
+
function hasExtendsShadowOf(cls: IntrospectedBaseClass, prerequisite: IntrospectedBaseClass, name: string): boolean {
|
|
61
|
+
let current = cls.resolveParents().extends();
|
|
62
|
+
while (current) {
|
|
63
|
+
const node = current.node;
|
|
64
|
+
if (node !== prerequisite) {
|
|
65
|
+
const hasOwn = [...node.props, ...node.fields, ...node.members].some((m) => m.name === name);
|
|
66
|
+
if (hasOwn) return true;
|
|
67
|
+
}
|
|
68
|
+
current = current.extends();
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveNullableProperties(cls: IntrospectedBaseClass): void {
|
|
74
|
+
for (const prop of cls.props) {
|
|
75
|
+
if (prop.type instanceof NullableType) continue;
|
|
76
|
+
|
|
77
|
+
const getterName = prop.getter ?? `get_${prop.name}`;
|
|
78
|
+
const getter = cls.members.find((m) => m.name === getterName && !(m instanceof IntrospectedStaticClassFunction));
|
|
79
|
+
|
|
80
|
+
if (getter instanceof IntrospectedClassFunction && getter.return() instanceof NullableType) {
|
|
81
|
+
prop.type = makeNullable(prop.type);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
50
86
|
/**
|
|
51
87
|
* Represents a signal with metadata
|
|
52
88
|
*/
|
|
@@ -75,6 +111,8 @@ export class IntrospectedClassFunction<
|
|
|
75
111
|
returnTypeDoc?: string | null;
|
|
76
112
|
/** If this function was generated from a signal, stores the signal name. */
|
|
77
113
|
signalOrigin?: string;
|
|
114
|
+
/** GIR glib:finish-func attribute: name of the function that finishes this async operation. */
|
|
115
|
+
finishFuncName?: string;
|
|
78
116
|
|
|
79
117
|
generics: Generic[] = [];
|
|
80
118
|
|
|
@@ -143,6 +181,7 @@ export class IntrospectedClassFunction<
|
|
|
143
181
|
|
|
144
182
|
fn.generics = [...this.generics];
|
|
145
183
|
fn.returnTypeDoc = this.returnTypeDoc;
|
|
184
|
+
fn.finishFuncName = this.finishFuncName;
|
|
146
185
|
|
|
147
186
|
if (interfaceParent) {
|
|
148
187
|
fn.interfaceParent = interfaceParent;
|
|
@@ -177,6 +216,10 @@ export class IntrospectedClassFunction<
|
|
|
177
216
|
// Convert the function to a class function
|
|
178
217
|
const { raw_name: name, output_parameters, parameters, return_type, doc, isIntrospectable } = fn;
|
|
179
218
|
|
|
219
|
+
// A function with shadowed-by is superseded by the shadowing function (which uses `shadows`)
|
|
220
|
+
// and takes the original name. Do not emit the shadowed function to avoid duplicate declarations.
|
|
221
|
+
const isShadowedBy = element.$["shadowed-by"] != null;
|
|
222
|
+
|
|
180
223
|
const classFn = new IntrospectedClassFunction({
|
|
181
224
|
parent,
|
|
182
225
|
name,
|
|
@@ -184,11 +227,12 @@ export class IntrospectedClassFunction<
|
|
|
184
227
|
parameters,
|
|
185
228
|
return_type,
|
|
186
229
|
doc,
|
|
187
|
-
isIntrospectable,
|
|
230
|
+
isIntrospectable: isIntrospectable && !isShadowedBy,
|
|
188
231
|
});
|
|
189
232
|
|
|
190
233
|
classFn.returnTypeDoc = fn.returnTypeDoc;
|
|
191
234
|
classFn.generics = [...fn.generics];
|
|
235
|
+
classFn.finishFuncName = element.$["glib:finish-func"];
|
|
192
236
|
|
|
193
237
|
return classFn;
|
|
194
238
|
}
|
|
@@ -396,6 +440,8 @@ export class IntrospectedStaticClassFunction extends IntrospectedClassFunction {
|
|
|
396
440
|
// Convert the function to a static class function
|
|
397
441
|
const { raw_name: name, output_parameters, parameters, return_type, doc, isIntrospectable } = fn;
|
|
398
442
|
|
|
443
|
+
const isShadowedBy = m.$["shadowed-by"] != null;
|
|
444
|
+
|
|
399
445
|
return new IntrospectedStaticClassFunction({
|
|
400
446
|
parent,
|
|
401
447
|
name,
|
|
@@ -403,7 +449,7 @@ export class IntrospectedStaticClassFunction extends IntrospectedClassFunction {
|
|
|
403
449
|
parameters,
|
|
404
450
|
return_type,
|
|
405
451
|
doc,
|
|
406
|
-
isIntrospectable,
|
|
452
|
+
isIntrospectable: isIntrospectable && !isShadowedBy,
|
|
407
453
|
});
|
|
408
454
|
}
|
|
409
455
|
}
|
|
@@ -853,13 +899,19 @@ export class IntrospectedClass extends IntrospectedBaseClass {
|
|
|
853
899
|
});
|
|
854
900
|
}
|
|
855
901
|
|
|
856
|
-
//
|
|
857
|
-
//
|
|
902
|
+
// An interface's <prerequisite> of class type is always satisfied by the
|
|
903
|
+
// implementing class's actual parent chain — those members are already
|
|
904
|
+
// inherited via TS class inheritance, so we don't re-emit them. The one
|
|
905
|
+
// case we still need to handle: when the parent chain has a same-named
|
|
906
|
+
// member with an incompatible signature/type, we re-emit the interface's
|
|
907
|
+
// version so `filterConflicts` / `filterFunctionConflict` can broaden or
|
|
908
|
+
// override it to satisfy both `extends` and `implements` simultaneously.
|
|
858
909
|
for (const implemented of resolution.implements()) {
|
|
859
910
|
const extended = implemented.extends();
|
|
860
911
|
if (extended?.node instanceof IntrospectedClass) {
|
|
861
912
|
for (const item of getItems(extended.node)) {
|
|
862
913
|
if (items.has(item.name) || !validate(item)) continue;
|
|
914
|
+
if (!hasExtendsShadowOf(this, extended.node, item.name)) continue;
|
|
863
915
|
items.set(item.name, item);
|
|
864
916
|
}
|
|
865
917
|
}
|
|
@@ -1036,6 +1088,7 @@ export class IntrospectedClass extends IntrospectedBaseClass {
|
|
|
1036
1088
|
IntrospectedClass.parseBasicProperties(element, clazz, ns, options);
|
|
1037
1089
|
IntrospectedClass.parseResolveNames(element, clazz, ns, name);
|
|
1038
1090
|
IntrospectedClass.parseInheritanceAndMembers(element, clazz, ns, options);
|
|
1091
|
+
resolveNullableProperties(clazz);
|
|
1039
1092
|
|
|
1040
1093
|
return clazz;
|
|
1041
1094
|
}
|
|
@@ -1342,6 +1395,7 @@ export class IntrospectedInterface extends IntrospectedBaseClass {
|
|
|
1342
1395
|
IntrospectedInterface.parseInterfaceBasicProperties(element, iface, namespace, options);
|
|
1343
1396
|
IntrospectedInterface.parseInterfaceResolveNames(element, iface, namespace, name);
|
|
1344
1397
|
IntrospectedInterface.parseInterfaceMembers(element, iface, namespace, options);
|
|
1398
|
+
resolveNullableProperties(iface);
|
|
1345
1399
|
|
|
1346
1400
|
return iface;
|
|
1347
1401
|
}
|
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 { makeNullable, 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 = makeNullable(property.type);
|
|
214
|
+
}
|
|
215
|
+
|
|
201
216
|
return property;
|
|
202
217
|
}
|
|
203
218
|
}
|
package/src/gir/registry.ts
CHANGED
|
@@ -8,7 +8,6 @@ import { inject } from "../injections/inject.ts";
|
|
|
8
8
|
import type { GeneratorConstructor, OptionsGeneration, OptionsTransform } from "../types/index.ts";
|
|
9
9
|
import { TwoKeyMap } from "../util.ts";
|
|
10
10
|
import { ClassVisitor } from "../validators/class.ts";
|
|
11
|
-
import { FunctionParametersVisitor } from "../validators/function-parameters.ts";
|
|
12
11
|
import { InterfaceVisitor } from "../validators/interface.ts";
|
|
13
12
|
import type { GirVisitor } from "../visitor.ts";
|
|
14
13
|
import type { IntrospectedNamespace } from "./namespace.ts";
|
|
@@ -104,28 +103,33 @@ export class NSRegistry {
|
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
transform(options: OptionsTransform) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
// In tolerant external-deps mode (with --allow-missing-deps) the core namespaces
|
|
107
|
+
// may not be loaded. Sync their package_version only when actually present;
|
|
108
|
+
// generify/inject still run on whatever IS loaded (other modules' transformations
|
|
109
|
+
// don't depend on GLib being in the registry).
|
|
110
|
+
const GLib = this.namespace("GLib", "2.0");
|
|
111
|
+
const Gio = this.namespace("Gio", "2.0");
|
|
112
|
+
const GObject = this.namespace("GObject", "2.0");
|
|
110
113
|
|
|
111
114
|
// These follow the GLib version.
|
|
112
|
-
Gio.package_version = [...GLib.package_version];
|
|
113
|
-
GObject.package_version = [...GLib.package_version];
|
|
115
|
+
if (GLib && Gio) Gio.package_version = [...GLib.package_version];
|
|
116
|
+
if (GLib && GObject) GObject.package_version = [...GLib.package_version];
|
|
114
117
|
|
|
115
118
|
const interfaceVisitor = new InterfaceVisitor();
|
|
119
|
+
|
|
116
120
|
this.registerTransformation(interfaceVisitor);
|
|
117
121
|
|
|
118
122
|
const classVisitor = new ClassVisitor();
|
|
119
|
-
this.registerTransformation(classVisitor);
|
|
120
123
|
|
|
121
|
-
|
|
122
|
-
this.registerTransformation(enumParamsVisitor);
|
|
124
|
+
this.registerTransformation(classVisitor);
|
|
123
125
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
if (GLib && Gio) {
|
|
127
|
+
console.log("Adding generics...");
|
|
128
|
+
generify(this, options.inferGenerics);
|
|
126
129
|
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
console.log("Injecting types...");
|
|
131
|
+
inject(this);
|
|
132
|
+
}
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
defaultVersionOf(name: string): string | null {
|
package/src/gir/signal.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { GirDirection } from "@gi.ts/parser";
|
|
|
3
3
|
import type { FormatGenerator } from "../generators/generator.ts";
|
|
4
4
|
import {
|
|
5
5
|
ArrayType,
|
|
6
|
+
makeNullable,
|
|
6
7
|
NativeType,
|
|
7
8
|
NullableType,
|
|
8
9
|
NumberType,
|
|
@@ -171,7 +172,7 @@ export class IntrospectedSignal extends IntrospectedClassMember<IntrospectedClas
|
|
|
171
172
|
}
|
|
172
173
|
} else {
|
|
173
174
|
if (isOptional) {
|
|
174
|
-
params.push(p.copy({ type:
|
|
175
|
+
params.push(p.copy({ type: makeNullable(type), isOptional: false }));
|
|
175
176
|
} else {
|
|
176
177
|
params.push(p);
|
|
177
178
|
}
|
package/src/gir.ts
CHANGED
|
@@ -23,7 +23,7 @@ export enum ConflictType {
|
|
|
23
23
|
|
|
24
24
|
import { type ConsoleReporter, LazyReporter } from "@ts-for-gir/reporter";
|
|
25
25
|
import type { IntrospectedField, IntrospectedProperty } from "./gir/property.ts";
|
|
26
|
-
import type {
|
|
26
|
+
import type { OptionsGeneration } from "./types/index.ts";
|
|
27
27
|
import { isInvalid, sanitizeIdentifierName, sanitizeNamespace } from "./utils/naming.ts";
|
|
28
28
|
|
|
29
29
|
export abstract class TypeExpression {
|
|
@@ -37,10 +37,10 @@ export abstract class TypeExpression {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
abstract rewrap(type: TypeExpression): TypeExpression;
|
|
40
|
-
abstract resolve(namespace: IntrospectedNamespace, options:
|
|
40
|
+
abstract resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression;
|
|
41
41
|
|
|
42
|
-
abstract print(namespace: IntrospectedNamespace, options:
|
|
43
|
-
rootPrint(namespace: IntrospectedNamespace, options:
|
|
42
|
+
abstract print(namespace: IntrospectedNamespace, options: OptionsGeneration): string;
|
|
43
|
+
rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
44
44
|
return this.print(namespace, options);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -89,7 +89,7 @@ export class TypeIdentifier extends TypeExpression {
|
|
|
89
89
|
return new TypeIdentifier(sanitizeIdentifierName(this.namespace, this.name), sanitizeNamespace(this.namespace));
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
protected _resolve(namespace: IntrospectedNamespace, options:
|
|
92
|
+
protected _resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeIdentifier | null {
|
|
93
93
|
const name: string = sanitizeIdentifierName(null, this.name);
|
|
94
94
|
const unresolvedNamespaceName = this.namespace;
|
|
95
95
|
|
|
@@ -183,11 +183,11 @@ export class TypeIdentifier extends TypeExpression {
|
|
|
183
183
|
return null;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
resolveIdentifier(namespace: IntrospectedNamespace, options:
|
|
186
|
+
resolveIdentifier(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeIdentifier | null {
|
|
187
187
|
return this._resolve(namespace, options);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
resolve(namespace: IntrospectedNamespace, options:
|
|
190
|
+
resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
|
|
191
191
|
const resolved = this._resolve(namespace, options);
|
|
192
192
|
|
|
193
193
|
// Generally if we can't resolve a type it is not introspectable,
|
|
@@ -199,7 +199,7 @@ export class TypeIdentifier extends TypeExpression {
|
|
|
199
199
|
return new TypeIdentifier(name, namespace);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
print(namespace: IntrospectedNamespace, _options:
|
|
202
|
+
print(namespace: IntrospectedNamespace, _options: OptionsGeneration): string {
|
|
203
203
|
if (namespace.hasSymbol(this.namespace) && this.namespace !== namespace.namespace) {
|
|
204
204
|
// TODO: Move to TypeScript generator...
|
|
205
205
|
// Libraries like zbar have classes named things like "Gtk"
|
|
@@ -248,11 +248,11 @@ export class ModuleTypeIdentifier extends TypeIdentifier {
|
|
|
248
248
|
);
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
protected _resolve(_namespace: IntrospectedNamespace, _options:
|
|
251
|
+
protected _resolve(_namespace: IntrospectedNamespace, _options: OptionsGeneration): ModuleTypeIdentifier | null {
|
|
252
252
|
return this;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
print(namespace: IntrospectedNamespace, _options:
|
|
255
|
+
print(namespace: IntrospectedNamespace, _options: OptionsGeneration): string {
|
|
256
256
|
if (namespace.namespace === this.namespace) {
|
|
257
257
|
return `${this.moduleName}.${this.name}`;
|
|
258
258
|
} else {
|
|
@@ -269,7 +269,7 @@ export class ClassStructTypeIdentifier extends TypeIdentifier {
|
|
|
269
269
|
return type instanceof ClassStructTypeIdentifier && super.equals(type);
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
print(namespace: IntrospectedNamespace, _options:
|
|
272
|
+
print(namespace: IntrospectedNamespace, _options: OptionsGeneration): string {
|
|
273
273
|
if (namespace.namespace === this.namespace) {
|
|
274
274
|
// TODO: Mapping to invalid names should happen at the generator level...
|
|
275
275
|
return `typeof ${isInvalid(this.name) ? `__${this.name}` : this.name}`;
|
|
@@ -287,7 +287,7 @@ export class GenerifiedTypeIdentifier extends TypeIdentifier {
|
|
|
287
287
|
this.generics = generics;
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
print(namespace: IntrospectedNamespace, options:
|
|
290
|
+
print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
291
291
|
const Generics = this.generics.map((generic) => generic.print(namespace, options)).join(", ");
|
|
292
292
|
|
|
293
293
|
if (namespace.namespace === this.namespace) {
|
|
@@ -297,7 +297,7 @@ export class GenerifiedTypeIdentifier extends TypeIdentifier {
|
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
_resolve(namespace: IntrospectedNamespace, options:
|
|
300
|
+
_resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeIdentifier | null {
|
|
301
301
|
const iden = super._resolve(namespace, options);
|
|
302
302
|
|
|
303
303
|
if (iden) {
|
|
@@ -309,9 +309,9 @@ export class GenerifiedTypeIdentifier extends TypeIdentifier {
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
export class NativeType extends TypeExpression {
|
|
312
|
-
readonly expression: (options?:
|
|
312
|
+
readonly expression: (options?: OptionsGeneration) => string;
|
|
313
313
|
|
|
314
|
-
constructor(expression: ((options?:
|
|
314
|
+
constructor(expression: ((options?: OptionsGeneration) => string) | string) {
|
|
315
315
|
super();
|
|
316
316
|
this.expression = typeof expression === "string" ? () => expression : expression;
|
|
317
317
|
}
|
|
@@ -324,11 +324,11 @@ export class NativeType extends TypeExpression {
|
|
|
324
324
|
return this;
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
print(_namespace: IntrospectedNamespace, options:
|
|
327
|
+
print(_namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
328
328
|
return this.expression(options);
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
equals(type: TypeExpression, options?:
|
|
331
|
+
equals(type: TypeExpression, options?: OptionsGeneration): boolean {
|
|
332
332
|
return type instanceof NativeType && this.expression(options) === type.expression(options);
|
|
333
333
|
}
|
|
334
334
|
|
|
@@ -336,7 +336,7 @@ export class NativeType extends TypeExpression {
|
|
|
336
336
|
return this;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
static withGenerator(generator: (options?:
|
|
339
|
+
static withGenerator(generator: (options?: OptionsGeneration) => string): TypeExpression {
|
|
340
340
|
return new NativeType(generator);
|
|
341
341
|
}
|
|
342
342
|
|
|
@@ -361,17 +361,17 @@ export class OrType extends TypeExpression {
|
|
|
361
361
|
return this;
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
resolve(namespace: IntrospectedNamespace, options:
|
|
364
|
+
resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
|
|
365
365
|
const [type, ...types] = this.types;
|
|
366
366
|
|
|
367
367
|
return new OrType(type.resolve(namespace, options), ...types.map((t) => t.resolve(namespace, options)));
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
print(namespace: IntrospectedNamespace, options:
|
|
370
|
+
print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
371
371
|
return `(${this.types.map((t) => t.print(namespace, options)).join(" | ")})`;
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
-
rootPrint(namespace: IntrospectedNamespace, options:
|
|
374
|
+
rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
375
375
|
return `${this.types.map((t) => t.print(namespace, options)).join(" | ")}`;
|
|
376
376
|
}
|
|
377
377
|
|
|
@@ -385,15 +385,15 @@ export class OrType extends TypeExpression {
|
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
export class TupleType extends OrType {
|
|
388
|
-
print(namespace: IntrospectedNamespace, options:
|
|
388
|
+
print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
389
389
|
return `[${this.types.map((t) => t.print(namespace, options)).join(", ")}]`;
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
-
rootPrint(namespace: IntrospectedNamespace, options:
|
|
392
|
+
rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
393
393
|
return this.print(namespace, options);
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
-
resolve(namespace: IntrospectedNamespace, options:
|
|
396
|
+
resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
|
|
397
397
|
const [type, ...types] = this.types;
|
|
398
398
|
|
|
399
399
|
return new TupleType(type.resolve(namespace, options), ...types.map((t) => t.resolve(namespace, options)));
|
|
@@ -413,7 +413,7 @@ export class BinaryType extends OrType {
|
|
|
413
413
|
return this;
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
-
resolve(namespace: IntrospectedNamespace, options:
|
|
416
|
+
resolve(namespace: IntrospectedNamespace, options: OptionsGeneration) {
|
|
417
417
|
return new BinaryType(this.a.resolve(namespace, options), this.b.resolve(namespace, options));
|
|
418
418
|
}
|
|
419
419
|
|
|
@@ -465,7 +465,7 @@ export class FunctionType extends TypeExpression {
|
|
|
465
465
|
return this;
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
resolve(namespace: IntrospectedNamespace, options:
|
|
468
|
+
resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
|
|
469
469
|
return new FunctionType(
|
|
470
470
|
Object.fromEntries(
|
|
471
471
|
Object.entries(this.parameterTypes).map(([k, p]) => {
|
|
@@ -476,7 +476,7 @@ export class FunctionType extends TypeExpression {
|
|
|
476
476
|
);
|
|
477
477
|
}
|
|
478
478
|
|
|
479
|
-
rootPrint(namespace: IntrospectedNamespace, options:
|
|
479
|
+
rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
480
480
|
const Parameters = Object.entries(this.parameterTypes)
|
|
481
481
|
.map(([k, v]) => {
|
|
482
482
|
return `${k}: ${v.rootPrint(namespace, options)}`;
|
|
@@ -486,7 +486,7 @@ export class FunctionType extends TypeExpression {
|
|
|
486
486
|
return `(${Parameters}) => ${this.returnType.print(namespace, options)}`;
|
|
487
487
|
}
|
|
488
488
|
|
|
489
|
-
print(namespace: IntrospectedNamespace, options:
|
|
489
|
+
print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
490
490
|
return `(${this.rootPrint(namespace, options)})`;
|
|
491
491
|
}
|
|
492
492
|
}
|
|
@@ -548,7 +548,7 @@ export class GenerifiedType extends TypeExpression {
|
|
|
548
548
|
this.generic = generic;
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
-
resolve(namespace: IntrospectedNamespace, options:
|
|
551
|
+
resolve(namespace: IntrospectedNamespace, options: OptionsGeneration) {
|
|
552
552
|
return new GenerifiedType(this.type.resolve(namespace, options), this.generic);
|
|
553
553
|
}
|
|
554
554
|
|
|
@@ -556,11 +556,11 @@ export class GenerifiedType extends TypeExpression {
|
|
|
556
556
|
return this.type;
|
|
557
557
|
}
|
|
558
558
|
|
|
559
|
-
rootPrint(namespace: IntrospectedNamespace, options:
|
|
559
|
+
rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration) {
|
|
560
560
|
return this.type.rootPrint(namespace, options);
|
|
561
561
|
}
|
|
562
562
|
|
|
563
|
-
print(namespace: IntrospectedNamespace, options:
|
|
563
|
+
print(namespace: IntrospectedNamespace, options: OptionsGeneration) {
|
|
564
564
|
return `${this.type.print(namespace, options)}<${this.generic.print()}>`;
|
|
565
565
|
}
|
|
566
566
|
|
|
@@ -624,7 +624,7 @@ export class NullableType extends BinaryType {
|
|
|
624
624
|
}
|
|
625
625
|
|
|
626
626
|
rewrap(type: TypeExpression): TypeExpression {
|
|
627
|
-
return
|
|
627
|
+
return makeNullable(this.a.rewrap(type));
|
|
628
628
|
}
|
|
629
629
|
|
|
630
630
|
get type() {
|
|
@@ -632,6 +632,12 @@ export class NullableType extends BinaryType {
|
|
|
632
632
|
}
|
|
633
633
|
}
|
|
634
634
|
|
|
635
|
+
export function makeNullable(type: TypeExpression) {
|
|
636
|
+
if (type === RawPointerType) return NullType;
|
|
637
|
+
if (type === AnyType) return AnyType;
|
|
638
|
+
return new NullableType(type);
|
|
639
|
+
}
|
|
640
|
+
|
|
635
641
|
export class PromiseType extends TypeExpression {
|
|
636
642
|
type: TypeExpression;
|
|
637
643
|
|
|
@@ -652,11 +658,11 @@ export class PromiseType extends TypeExpression {
|
|
|
652
658
|
return new PromiseType(this.type.rewrap(type));
|
|
653
659
|
}
|
|
654
660
|
|
|
655
|
-
resolve(namespace: IntrospectedNamespace, options:
|
|
661
|
+
resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
|
|
656
662
|
return new PromiseType(this.type.resolve(namespace, options));
|
|
657
663
|
}
|
|
658
664
|
|
|
659
|
-
print(namespace: IntrospectedNamespace, options:
|
|
665
|
+
print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
660
666
|
if (this.type.equals(VoidType)) {
|
|
661
667
|
return "globalThis.Promise<void>";
|
|
662
668
|
}
|
|
@@ -664,7 +670,7 @@ export class PromiseType extends TypeExpression {
|
|
|
664
670
|
return `globalThis.Promise<${this.type.print(namespace, options)}>`;
|
|
665
671
|
}
|
|
666
672
|
|
|
667
|
-
rootPrint(namespace: IntrospectedNamespace, options:
|
|
673
|
+
rootPrint(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
668
674
|
return this.print(namespace, options);
|
|
669
675
|
}
|
|
670
676
|
}
|
|
@@ -703,13 +709,13 @@ export class TypeConflict extends TypeExpression {
|
|
|
703
709
|
return true;
|
|
704
710
|
}
|
|
705
711
|
|
|
706
|
-
resolve(namespace: IntrospectedNamespace, options:
|
|
712
|
+
resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
|
|
707
713
|
const resolvedType = this.type.resolve(namespace, options);
|
|
708
714
|
const typeString = resolvedType.print(namespace, options);
|
|
709
715
|
throw new Error(`Type conflict was not resolved for ${typeString} in ${namespace.namespace}`);
|
|
710
716
|
}
|
|
711
717
|
|
|
712
|
-
print(namespace: IntrospectedNamespace, options:
|
|
718
|
+
print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
713
719
|
const resolvedType = this.type.resolve(namespace, options);
|
|
714
720
|
const typeString = resolvedType.print(namespace, options);
|
|
715
721
|
throw new Error(`Type conflict was not resolved for ${typeString} in ${namespace.namespace}`);
|
|
@@ -750,7 +756,7 @@ export class ClosureType extends TypeExpression {
|
|
|
750
756
|
return this;
|
|
751
757
|
}
|
|
752
758
|
|
|
753
|
-
resolve(namespace: IntrospectedNamespace, options:
|
|
759
|
+
resolve(namespace: IntrospectedNamespace, options: OptionsGeneration) {
|
|
754
760
|
const { user_data, type } = this;
|
|
755
761
|
|
|
756
762
|
return ClosureType.new({
|
|
@@ -759,7 +765,7 @@ export class ClosureType extends TypeExpression {
|
|
|
759
765
|
});
|
|
760
766
|
}
|
|
761
767
|
|
|
762
|
-
print(namespace: IntrospectedNamespace, options:
|
|
768
|
+
print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
763
769
|
return this.type.print(namespace, options);
|
|
764
770
|
}
|
|
765
771
|
|
|
@@ -807,7 +813,7 @@ export class ArrayType extends TypeExpression {
|
|
|
807
813
|
return false;
|
|
808
814
|
}
|
|
809
815
|
|
|
810
|
-
resolve(namespace: IntrospectedNamespace, options:
|
|
816
|
+
resolve(namespace: IntrospectedNamespace, options: OptionsGeneration): TypeExpression {
|
|
811
817
|
const { type, arrayDepth, length } = this;
|
|
812
818
|
return ArrayType.new({
|
|
813
819
|
type: type.resolve(namespace, options),
|
|
@@ -816,7 +822,7 @@ export class ArrayType extends TypeExpression {
|
|
|
816
822
|
});
|
|
817
823
|
}
|
|
818
824
|
|
|
819
|
-
print(namespace: IntrospectedNamespace, options:
|
|
825
|
+
print(namespace: IntrospectedNamespace, options: OptionsGeneration): string {
|
|
820
826
|
const depth = this.arrayDepth;
|
|
821
827
|
let typeSuffix: string = "";
|
|
822
828
|
|
|
@@ -861,9 +867,13 @@ export const Uint8ArrayType = new NativeType("Uint8Array");
|
|
|
861
867
|
export const BooleanType = new NativeType("boolean");
|
|
862
868
|
export const StringType = new NativeType("string");
|
|
863
869
|
export const NumberType = new NativeType("number");
|
|
870
|
+
export const BigintOrNumberType = new BinaryType(new NativeType("bigint"), NumberType);
|
|
864
871
|
export const NullType = new NativeType("null");
|
|
865
872
|
export const VoidType = new NativeType("void");
|
|
866
873
|
export const UnknownType = new NativeType("unknown");
|
|
867
874
|
export const AnyFunctionType = new NativeType("(...args: any[]) => any");
|
|
875
|
+
// Distinct from NeverType, so that we can transform it into NullType when
|
|
876
|
+
// marshalled from C to JS
|
|
877
|
+
export const RawPointerType = new NativeType("never");
|
|
868
878
|
|
|
869
879
|
export type GirClassField = IntrospectedProperty | IntrospectedField;
|
|
@@ -23,6 +23,8 @@ export interface OptionsGeneration extends OptionsBase {
|
|
|
23
23
|
npmScope: string;
|
|
24
24
|
/** Uses the workspace protocol for the generated packages which can be used with package managers like Yarn and PNPM */
|
|
25
25
|
workspace: boolean;
|
|
26
|
+
/** Format used for dependency version specifiers in generated package.json files */
|
|
27
|
+
depVersionFormat?: "workspace" | "caret" | "any" | "exact";
|
|
26
28
|
/** Disable GLib.Variant class with string parsing */
|
|
27
29
|
noAdvancedVariants: boolean;
|
|
28
30
|
/**
|
|
@@ -62,4 +64,25 @@ export interface OptionsGeneration extends OptionsBase {
|
|
|
62
64
|
merge?: boolean;
|
|
63
65
|
/** Directory containing pre-generated TypeDoc JSON files for merge mode (from 'ts-for-gir json') */
|
|
64
66
|
jsonDir?: string;
|
|
67
|
+
/**
|
|
68
|
+
* External-deps mode: emit `import` statements that reference dependency types from
|
|
69
|
+
* already-installed npm packages (e.g. `@girs/glib-2.0`) instead of regenerating them.
|
|
70
|
+
* Designed for project-local GIRs (Vala bridges etc.) where the surrounding `@girs/*`
|
|
71
|
+
* ecosystem is already in node_modules. No GJS supporting files, no index aggregator.
|
|
72
|
+
*/
|
|
73
|
+
externalDeps: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* In `externalDeps` mode, allow generation to proceed even when some transitive dep GIRs
|
|
76
|
+
* cannot be found. Default is strict: missing deps abort the run, since divergent dep
|
|
77
|
+
* availability between environments (dev with -devel packages vs CI without) would
|
|
78
|
+
* silently produce inconsistent generated `.d.ts` output.
|
|
79
|
+
*/
|
|
80
|
+
allowMissingDeps: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Override the default `<npmScope>/<importName>` mapping for individual namespaces when
|
|
83
|
+
* resolving external dependency imports in `externalDeps` mode.
|
|
84
|
+
*
|
|
85
|
+
* Example: `{ Soup: '@girs/soup-3.0', GLib: '@girs/glib-2.0' }`
|
|
86
|
+
*/
|
|
87
|
+
externalPackages?: Record<string, string>;
|
|
65
88
|
}
|
package/src/types/user-config.ts
CHANGED
|
@@ -28,6 +28,16 @@ export interface UserConfig {
|
|
|
28
28
|
npmScope: string;
|
|
29
29
|
/** Uses the workspace protocol for the generated packages which can be used with package managers like Yarn and PNPM */
|
|
30
30
|
workspace: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Format used for dependency version specifiers in generated package.json files.
|
|
33
|
+
* - `workspace`: emit `workspace:^` (yarn/pnpm; npm only when the ref matches a registered workspace)
|
|
34
|
+
* - `caret`: emit `^<version>` (all managers, falls back to registry for dangling refs)
|
|
35
|
+
* - `any`: emit `*` (all managers, most permissive)
|
|
36
|
+
* - `exact`: emit `<version>` (all managers, no range)
|
|
37
|
+
*
|
|
38
|
+
* If unset, defaults to `workspace` when `workspace: true`, else `caret`.
|
|
39
|
+
*/
|
|
40
|
+
depVersionFormat?: "workspace" | "caret" | "any" | "exact";
|
|
31
41
|
/**
|
|
32
42
|
* Only use the version prefix for the ambient module exports.
|
|
33
43
|
* This is useful if, for whatever reason, you want to use different library versions of the same library in your project.
|
|
@@ -67,4 +77,24 @@ export interface UserConfig {
|
|
|
67
77
|
merge?: boolean;
|
|
68
78
|
/** Directory containing pre-generated TypeDoc JSON files for merge mode (from 'ts-for-gir json') */
|
|
69
79
|
jsonDir?: string;
|
|
80
|
+
/**
|
|
81
|
+
* External-deps mode: emit `import` statements that reference dependency types from
|
|
82
|
+
* already-installed npm packages (e.g. `@girs/glib-2.0`) instead of regenerating them.
|
|
83
|
+
* Designed for project-local GIRs (Vala bridges etc.) where the surrounding `@girs/*`
|
|
84
|
+
* ecosystem is already in node_modules.
|
|
85
|
+
*/
|
|
86
|
+
externalDeps: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Allow `externalDeps` generation to proceed when some transitive dep GIRs are missing.
|
|
89
|
+
* Default is strict: missing deps cause an error to prevent silent type-quality drift
|
|
90
|
+
* between environments with and without system GIR -devel packages installed.
|
|
91
|
+
*/
|
|
92
|
+
allowMissingDeps: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Override the default `<npmScope>/<importName>` mapping for individual namespaces when
|
|
95
|
+
* resolving external dependency imports in `externalDeps` mode.
|
|
96
|
+
*
|
|
97
|
+
* Example: `{ Soup: '@girs/soup-3.0', GLib: '@girs/glib-2.0' }`
|
|
98
|
+
*/
|
|
99
|
+
externalPackages?: Record<string, string>;
|
|
70
100
|
}
|
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);
|
package/src/utils/generation.ts
CHANGED
|
@@ -37,7 +37,9 @@ export function addTSDocCommentLines(lines: string[], indentCount = 0): string[]
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Adds an info comment, is used for debugging the generated types
|
|
40
|
+
* Adds an info comment, is used for debugging the generated types.
|
|
41
|
+
* One blank line before the comment, one after — readers see a single
|
|
42
|
+
* blank-line separator on each side without a double-blank gap.
|
|
41
43
|
*/
|
|
42
44
|
export function addInfoComment(comment?: string, indentCount = 0): string[] {
|
|
43
45
|
const def: string[] = [];
|
|
@@ -45,7 +47,6 @@ export function addInfoComment(comment?: string, indentCount = 0): string[] {
|
|
|
45
47
|
if (comment) {
|
|
46
48
|
def.push("");
|
|
47
49
|
def.push(`${indent}// ${comment}`);
|
|
48
|
-
def.push("");
|
|
49
50
|
}
|
|
50
51
|
return def;
|
|
51
52
|
}
|
|
@@ -58,7 +59,8 @@ export function mergeDescs(descs: string[], comment?: string, indentCount = 1):
|
|
|
58
59
|
const indent = generateIndent(indentCount);
|
|
59
60
|
|
|
60
61
|
for (const desc of descs) {
|
|
61
|
-
|
|
62
|
+
// Empty separator strings stay empty; only real content gets indented.
|
|
63
|
+
def.push(desc.length === 0 ? desc : `${indent}${desc}`);
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
if (def.length > 0) {
|
|
@@ -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/gir-parsing.ts
CHANGED
|
@@ -14,10 +14,11 @@ import { LazyReporter } from "@ts-for-gir/reporter";
|
|
|
14
14
|
import type { IntrospectedNamespace } from "../gir/namespace.ts";
|
|
15
15
|
import {
|
|
16
16
|
ArrayType,
|
|
17
|
+
BigintOrNumberType,
|
|
17
18
|
ClosureType,
|
|
18
19
|
GenerifiedTypeIdentifier,
|
|
20
|
+
makeNullable,
|
|
19
21
|
NativeType,
|
|
20
|
-
NullableType,
|
|
21
22
|
type TypeExpression,
|
|
22
23
|
TypeIdentifier,
|
|
23
24
|
VoidType,
|
|
@@ -238,13 +239,14 @@ export function getType(
|
|
|
238
239
|
parameter.$ &&
|
|
239
240
|
(parameter.$.direction === GirDirection.Inout || parameter.$.direction === GirDirection.Out) &&
|
|
240
241
|
(nullable || allowNone) &&
|
|
241
|
-
!(variableType instanceof NativeType)
|
|
242
|
+
!(variableType instanceof NativeType) &&
|
|
243
|
+
variableType !== BigintOrNumberType
|
|
242
244
|
) {
|
|
243
|
-
return
|
|
245
|
+
return makeNullable(variableType);
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
if ((!parameter.$?.direction || parameter.$.direction === GirDirection.In) && nullable) {
|
|
247
|
-
return
|
|
249
|
+
return makeNullable(variableType);
|
|
248
250
|
}
|
|
249
251
|
|
|
250
252
|
variableType.isPointer = isPointer;
|
package/src/utils/index.ts
CHANGED
package/src/utils/path.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { dirname, resolve } from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
//
|
|
7
|
-
|
|
4
|
+
// Resolves to the root directory of this package in dev mode.
|
|
5
|
+
// Wrapped in try/catch so a bundled consumer that lacks `import.meta.url`
|
|
6
|
+
// support degrades to "" instead of crashing at module load.
|
|
7
|
+
function resolvePackageDir(): string {
|
|
8
|
+
try {
|
|
9
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
10
|
+
} catch {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const __dirname = resolvePackageDir();
|
package/src/utils/types.ts
CHANGED
|
@@ -4,14 +4,21 @@ import type { IntrospectedNamespace } from "../gir/namespace.ts";
|
|
|
4
4
|
import {
|
|
5
5
|
AnyType,
|
|
6
6
|
ArrayType,
|
|
7
|
+
BigintOrNumberType,
|
|
7
8
|
BinaryType,
|
|
8
9
|
BooleanType,
|
|
9
10
|
NativeType,
|
|
10
11
|
NeverType,
|
|
12
|
+
NullableType,
|
|
13
|
+
NullType,
|
|
11
14
|
NumberType,
|
|
12
15
|
ObjectType,
|
|
16
|
+
OrType,
|
|
17
|
+
PromiseType,
|
|
18
|
+
RawPointerType,
|
|
13
19
|
StringType,
|
|
14
20
|
ThisType,
|
|
21
|
+
TupleType,
|
|
15
22
|
type TypeExpression,
|
|
16
23
|
TypeIdentifier,
|
|
17
24
|
Uint8ArrayType,
|
|
@@ -167,28 +174,33 @@ export function resolvePrimitiveType(name: string): TypeExpression | null {
|
|
|
167
174
|
case "gfloat":
|
|
168
175
|
case "gchar":
|
|
169
176
|
case "guint":
|
|
170
|
-
case "glong":
|
|
171
|
-
case "gulong":
|
|
172
177
|
case "gint":
|
|
173
178
|
case "guint8":
|
|
179
|
+
case "gdouble":
|
|
180
|
+
return NumberType;
|
|
181
|
+
case "glong":
|
|
182
|
+
case "gulong":
|
|
174
183
|
case "guint64":
|
|
175
184
|
case "gint64":
|
|
176
|
-
case "gdouble":
|
|
177
185
|
case "gssize":
|
|
178
186
|
case "gsize":
|
|
187
|
+
case "guintptr": // Integer of the same width as a pointer
|
|
179
188
|
case "time_t": // C standard library time type (seconds since Unix epoch)
|
|
180
189
|
case "ulong": // C standard library unsigned long type
|
|
181
|
-
return
|
|
190
|
+
return BigintOrNumberType;
|
|
182
191
|
case "gboolean":
|
|
183
192
|
return BooleanType;
|
|
184
|
-
case "gpointer":
|
|
185
|
-
|
|
193
|
+
case "gpointer":
|
|
194
|
+
// You can't use pointers. Pointer arguments are mostly not exposed
|
|
195
|
+
// in GJS, but any exposed pointer arguments are always marshalled
|
|
196
|
+
// as null pointers. If the argument is nullable, this will combine
|
|
197
|
+
// with `null` to produce `null`, but if the argument isn't nullable
|
|
198
|
+
// it's impossible to pass a valid parameter to the function.
|
|
199
|
+
return RawPointerType;
|
|
186
200
|
case "object": // Support TS "object"
|
|
187
201
|
return ObjectType;
|
|
188
202
|
case "va_list":
|
|
189
203
|
return AnyType;
|
|
190
|
-
case "guintptr": // You can't use pointers in GJS! (at least that I'm aware of)
|
|
191
|
-
return NeverType;
|
|
192
204
|
case "never": // Support TS "never"
|
|
193
205
|
return NeverType;
|
|
194
206
|
case "unknown": // Support TS "unknown"
|
|
@@ -243,6 +255,30 @@ export function resolveDirectedType(type: TypeExpression, direction: GirDirectio
|
|
|
243
255
|
return type;
|
|
244
256
|
}
|
|
245
257
|
}
|
|
258
|
+
} else if (type === BigintOrNumberType && direction === GirDirection.Out) {
|
|
259
|
+
// 64-bit integers accept number or bigint, but only return number to JS
|
|
260
|
+
return NumberType;
|
|
261
|
+
} else if (type === RawPointerType && direction === GirDirection.Out) {
|
|
262
|
+
// Raw pointers are always marshalled as JS null.
|
|
263
|
+
return NullType;
|
|
264
|
+
} else if (type instanceof PromiseType) {
|
|
265
|
+
// Propagate direction into the Promise's inner type so e.g. async
|
|
266
|
+
// functions returning 64-bit ints resolve to `Promise<number>` rather
|
|
267
|
+
// than `Promise<bigint | number>`.
|
|
268
|
+
const resolvedInner = resolveDirectedType(type.type, direction);
|
|
269
|
+
if (resolvedInner) return new PromiseType(resolvedInner);
|
|
270
|
+
} else if (type instanceof BinaryType && !(type instanceof NullableType)) {
|
|
271
|
+
// Walk through binary unions like `Promise<T> | void` (the dual-call
|
|
272
|
+
// async overload) so the inner types still get direction propagation.
|
|
273
|
+
// NullableType is skipped to preserve its subclass behaviour.
|
|
274
|
+
const a = resolveDirectedType(type.a, direction) ?? type.a;
|
|
275
|
+
const b = resolveDirectedType(type.b, direction) ?? type.b;
|
|
276
|
+
if (a !== type.a || b !== type.b) return new BinaryType(a, b);
|
|
277
|
+
} else if (type instanceof OrType && !(type instanceof BinaryType || type instanceof TupleType)) {
|
|
278
|
+
// flatten "bigint | number" out of another OR-type
|
|
279
|
+
const types = type.types.map((t) => resolveDirectedType(t, direction) ?? t);
|
|
280
|
+
if (types.length === 1) return types[0];
|
|
281
|
+
return new OrType(types[0], ...types.slice(1));
|
|
246
282
|
}
|
|
247
283
|
|
|
248
284
|
return null;
|
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) {
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { IntrospectedEnum } from "../gir/enum.ts";
|
|
2
|
-
import type { IntrospectedFunction } from "../gir/function.ts";
|
|
3
|
-
import type { IntrospectedBaseClass, IntrospectedClassFunction } from "../gir/introspected-classes.ts";
|
|
4
|
-
|
|
5
|
-
import { NullableType, TypeIdentifier } from "../gir.ts";
|
|
6
|
-
import { GirVisitor } from "../visitor.ts";
|
|
7
|
-
|
|
8
|
-
export class FunctionParametersVisitor extends GirVisitor {
|
|
9
|
-
/**
|
|
10
|
-
* Marks all enum parameters of a function as nullable,
|
|
11
|
-
* because GJS allows null values for enum parameters and treats them as a 0 value.
|
|
12
|
-
* See issue [#207](https://github.com/gjsify/ts-for-gir/issues/207).
|
|
13
|
-
*/
|
|
14
|
-
private makeEnumParamsNullable(node: IntrospectedFunction): IntrospectedFunction;
|
|
15
|
-
private makeEnumParamsNullable<T extends IntrospectedBaseClass | IntrospectedEnum>(
|
|
16
|
-
node: IntrospectedClassFunction<T>,
|
|
17
|
-
): IntrospectedClassFunction<T>;
|
|
18
|
-
private makeEnumParamsNullable(
|
|
19
|
-
node: IntrospectedFunction | IntrospectedClassFunction,
|
|
20
|
-
): IntrospectedFunction | IntrospectedClassFunction {
|
|
21
|
-
return node.copy({
|
|
22
|
-
parameters: node.parameters.map((param) => {
|
|
23
|
-
const type = param.type.deepUnwrap();
|
|
24
|
-
if (type instanceof TypeIdentifier) {
|
|
25
|
-
// Get the namespace where this type should be defined
|
|
26
|
-
const ns = node.namespace.assertInstalledImport(type.namespace);
|
|
27
|
-
|
|
28
|
-
// Check if the type is an enum
|
|
29
|
-
const isEnumType = !!ns.getEnum(type.name);
|
|
30
|
-
|
|
31
|
-
// If it is, make the parameter nullable
|
|
32
|
-
if (isEnumType) {
|
|
33
|
-
return param.copy({
|
|
34
|
-
type: new NullableType(param.type),
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return param;
|
|
39
|
-
}),
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
visitFunction = (node: IntrospectedFunction): IntrospectedFunction => {
|
|
44
|
-
return this.makeEnumParamsNullable(node);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
visitClassFunction = <T extends IntrospectedBaseClass | IntrospectedEnum>(
|
|
48
|
-
node: IntrospectedClassFunction<T>,
|
|
49
|
-
): IntrospectedClassFunction<T> => {
|
|
50
|
-
return this.makeEnumParamsNullable(node);
|
|
51
|
-
};
|
|
52
|
-
}
|