@gtkx/codegen 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -0
- package/dist/dsl/context.d.ts +104 -0
- package/dist/dsl/context.d.ts.map +1 -0
- package/dist/dsl/context.js +140 -0
- package/dist/dsl/context.js.map +1 -0
- package/dist/dsl/emit.d.ts +28 -0
- package/dist/dsl/emit.d.ts.map +1 -0
- package/dist/dsl/emit.js +41 -0
- package/dist/dsl/emit.js.map +1 -0
- package/dist/dsl/identifier.d.ts +28 -0
- package/dist/dsl/identifier.d.ts.map +1 -0
- package/dist/dsl/identifier.js +45 -0
- package/dist/dsl/identifier.js.map +1 -0
- package/dist/dsl/imports.d.ts +58 -0
- package/dist/dsl/imports.d.ts.map +1 -0
- package/dist/dsl/imports.js +107 -0
- package/dist/dsl/imports.js.map +1 -0
- package/dist/dsl/module.d.ts +53 -0
- package/dist/dsl/module.d.ts.map +1 -0
- package/dist/dsl/module.js +76 -0
- package/dist/dsl/module.js.map +1 -0
- package/dist/ffi/pipeline.d.ts +19 -0
- package/dist/ffi/pipeline.d.ts.map +1 -0
- package/dist/ffi/pipeline.js +107 -0
- package/dist/ffi/pipeline.js.map +1 -0
- package/dist/fingerprint.d.ts +30 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +33 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/gi-store.d.ts +34 -0
- package/dist/gi-store.d.ts.map +1 -0
- package/dist/gi-store.js +154 -0
- package/dist/gi-store.js.map +1 -0
- package/dist/gir/boxed.d.ts +66 -0
- package/dist/gir/boxed.d.ts.map +1 -0
- package/dist/gir/boxed.js +49 -0
- package/dist/gir/boxed.js.map +1 -0
- package/dist/gir/callback.d.ts +15 -0
- package/dist/gir/callback.d.ts.map +1 -0
- package/dist/gir/callback.js +17 -0
- package/dist/gir/callback.js.map +1 -0
- package/dist/gir/class.d.ts +49 -0
- package/dist/gir/class.d.ts.map +1 -0
- package/dist/gir/class.js +42 -0
- package/dist/gir/class.js.map +1 -0
- package/dist/gir/enum.d.ts +39 -0
- package/dist/gir/enum.d.ts.map +1 -0
- package/dist/gir/enum.js +24 -0
- package/dist/gir/enum.js.map +1 -0
- package/dist/gir/field.d.ts +27 -0
- package/dist/gir/field.d.ts.map +1 -0
- package/dist/gir/field.js +19 -0
- package/dist/gir/field.js.map +1 -0
- package/dist/gir/function.d.ts +47 -0
- package/dist/gir/function.d.ts.map +1 -0
- package/dist/gir/function.js +28 -0
- package/dist/gir/function.js.map +1 -0
- package/dist/gir/namespace.d.ts +65 -0
- package/dist/gir/namespace.d.ts.map +1 -0
- package/dist/gir/namespace.js +60 -0
- package/dist/gir/namespace.js.map +1 -0
- package/dist/gir/parameter.d.ts +82 -0
- package/dist/gir/parameter.d.ts.map +1 -0
- package/dist/gir/parameter.js +66 -0
- package/dist/gir/parameter.js.map +1 -0
- package/dist/gir/parse.d.ts +78 -0
- package/dist/gir/parse.d.ts.map +1 -0
- package/dist/gir/parse.js +155 -0
- package/dist/gir/parse.js.map +1 -0
- package/dist/gir/primitives.d.ts +36 -0
- package/dist/gir/primitives.d.ts.map +1 -0
- package/dist/gir/primitives.js +109 -0
- package/dist/gir/primitives.js.map +1 -0
- package/dist/gir/property.d.ts +29 -0
- package/dist/gir/property.d.ts.map +1 -0
- package/dist/gir/property.js +16 -0
- package/dist/gir/property.js.map +1 -0
- package/dist/gir/qualified-name.d.ts +37 -0
- package/dist/gir/qualified-name.d.ts.map +1 -0
- package/dist/gir/qualified-name.js +39 -0
- package/dist/gir/qualified-name.js.map +1 -0
- package/dist/gir/qualify.d.ts +32 -0
- package/dist/gir/qualify.d.ts.map +1 -0
- package/dist/gir/qualify.js +60 -0
- package/dist/gir/qualify.js.map +1 -0
- package/dist/gir/repository.d.ts +82 -0
- package/dist/gir/repository.d.ts.map +1 -0
- package/dist/gir/repository.js +122 -0
- package/dist/gir/repository.js.map +1 -0
- package/dist/gir/signal.d.ts +22 -0
- package/dist/gir/signal.d.ts.map +1 -0
- package/dist/gir/signal.js +20 -0
- package/dist/gir/signal.js.map +1 -0
- package/dist/gir/size.d.ts +67 -0
- package/dist/gir/size.d.ts.map +1 -0
- package/dist/gir/size.js +87 -0
- package/dist/gir/size.js.map +1 -0
- package/dist/gir/type-ref.d.ts +79 -0
- package/dist/gir/type-ref.d.ts.map +1 -0
- package/dist/gir/type-ref.js +101 -0
- package/dist/gir/type-ref.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx-store.d.ts +22 -0
- package/dist/jsx-store.d.ts.map +1 -0
- package/dist/jsx-store.js +40 -0
- package/dist/jsx-store.js.map +1 -0
- package/dist/react/compounds-meta.d.ts +18 -0
- package/dist/react/compounds-meta.d.ts.map +1 -0
- package/dist/react/compounds-meta.js +93 -0
- package/dist/react/compounds-meta.js.map +1 -0
- package/dist/react/compounds.d.ts +27 -0
- package/dist/react/compounds.d.ts.map +1 -0
- package/dist/react/compounds.js +164 -0
- package/dist/react/compounds.js.map +1 -0
- package/dist/react/internal.d.ts +14 -0
- package/dist/react/internal.d.ts.map +1 -0
- package/dist/react/internal.js +79 -0
- package/dist/react/internal.js.map +1 -0
- package/dist/react/jsx.d.ts +22 -0
- package/dist/react/jsx.d.ts.map +1 -0
- package/dist/react/jsx.js +134 -0
- package/dist/react/jsx.js.map +1 -0
- package/dist/react/pipeline.d.ts +13 -0
- package/dist/react/pipeline.d.ts.map +1 -0
- package/dist/react/pipeline.js +25 -0
- package/dist/react/pipeline.js.map +1 -0
- package/dist/react/props.d.ts +38 -0
- package/dist/react/props.d.ts.map +1 -0
- package/dist/react/props.js +231 -0
- package/dist/react/props.js.map +1 -0
- package/dist/react/slot-props.d.ts +10 -0
- package/dist/react/slot-props.d.ts.map +1 -0
- package/dist/react/slot-props.js +56 -0
- package/dist/react/slot-props.js.map +1 -0
- package/dist/react/widgets.d.ts +83 -0
- package/dist/react/widgets.d.ts.map +1 -0
- package/dist/react/widgets.js +152 -0
- package/dist/react/widgets.js.map +1 -0
- package/dist/runner.d.ts +55 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +60 -0
- package/dist/runner.js.map +1 -0
- package/dist/store-fs.d.ts +109 -0
- package/dist/store-fs.d.ts.map +1 -0
- package/dist/store-fs.js +98 -0
- package/dist/store-fs.js.map +1 -0
- package/dist/transpile.d.ts +32 -0
- package/dist/transpile.d.ts.map +1 -0
- package/dist/transpile.js +63 -0
- package/dist/transpile.js.map +1 -0
- package/dist/typecheck-store.d.ts +41 -0
- package/dist/typecheck-store.d.ts.map +1 -0
- package/dist/typecheck-store.js +102 -0
- package/dist/typecheck-store.js.map +1 -0
- package/dist/writers/alias.d.ts +13 -0
- package/dist/writers/alias.d.ts.map +1 -0
- package/dist/writers/alias.js +17 -0
- package/dist/writers/alias.js.map +1 -0
- package/dist/writers/async.d.ts +22 -0
- package/dist/writers/async.d.ts.map +1 -0
- package/dist/writers/async.js +48 -0
- package/dist/writers/async.js.map +1 -0
- package/dist/writers/boxed-constructor.d.ts +26 -0
- package/dist/writers/boxed-constructor.d.ts.map +1 -0
- package/dist/writers/boxed-constructor.js +85 -0
- package/dist/writers/boxed-constructor.js.map +1 -0
- package/dist/writers/boxed-field-accessor.d.ts +18 -0
- package/dist/writers/boxed-field-accessor.d.ts.map +1 -0
- package/dist/writers/boxed-field-accessor.js +260 -0
- package/dist/writers/boxed-field-accessor.js.map +1 -0
- package/dist/writers/boxed-layout.d.ts +27 -0
- package/dist/writers/boxed-layout.d.ts.map +1 -0
- package/dist/writers/boxed-layout.js +128 -0
- package/dist/writers/boxed-layout.js.map +1 -0
- package/dist/writers/boxed.d.ts +20 -0
- package/dist/writers/boxed.d.ts.map +1 -0
- package/dist/writers/boxed.js +71 -0
- package/dist/writers/boxed.js.map +1 -0
- package/dist/writers/callables.d.ts +124 -0
- package/dist/writers/callables.d.ts.map +1 -0
- package/dist/writers/callables.js +283 -0
- package/dist/writers/callables.js.map +1 -0
- package/dist/writers/callback.d.ts +16 -0
- package/dist/writers/callback.d.ts.map +1 -0
- package/dist/writers/callback.js +38 -0
- package/dist/writers/callback.js.map +1 -0
- package/dist/writers/class-struct-record.d.ts +31 -0
- package/dist/writers/class-struct-record.d.ts.map +1 -0
- package/dist/writers/class-struct-record.js +67 -0
- package/dist/writers/class-struct-record.js.map +1 -0
- package/dist/writers/class-struct.d.ts +22 -0
- package/dist/writers/class-struct.d.ts.map +1 -0
- package/dist/writers/class-struct.js +85 -0
- package/dist/writers/class-struct.js.map +1 -0
- package/dist/writers/class.d.ts +16 -0
- package/dist/writers/class.d.ts.map +1 -0
- package/dist/writers/class.js +307 -0
- package/dist/writers/class.js.map +1 -0
- package/dist/writers/constant.d.ts +15 -0
- package/dist/writers/constant.d.ts.map +1 -0
- package/dist/writers/constant.js +23 -0
- package/dist/writers/constant.js.map +1 -0
- package/dist/writers/constants.d.ts +15 -0
- package/dist/writers/constants.d.ts.map +1 -0
- package/dist/writers/constants.js +23 -0
- package/dist/writers/constants.js.map +1 -0
- package/dist/writers/constructor-props.d.ts +32 -0
- package/dist/writers/constructor-props.d.ts.map +1 -0
- package/dist/writers/constructor-props.js +110 -0
- package/dist/writers/constructor-props.js.map +1 -0
- package/dist/writers/enum.d.ts +21 -0
- package/dist/writers/enum.d.ts.map +1 -0
- package/dist/writers/enum.js +48 -0
- package/dist/writers/enum.js.map +1 -0
- package/dist/writers/function.d.ts +40 -0
- package/dist/writers/function.d.ts.map +1 -0
- package/dist/writers/function.js +127 -0
- package/dist/writers/function.js.map +1 -0
- package/dist/writers/gtype-binding.d.ts +19 -0
- package/dist/writers/gtype-binding.d.ts.map +1 -0
- package/dist/writers/gtype-binding.js +40 -0
- package/dist/writers/gtype-binding.js.map +1 -0
- package/dist/writers/inheritance.d.ts +48 -0
- package/dist/writers/inheritance.d.ts.map +1 -0
- package/dist/writers/inheritance.js +90 -0
- package/dist/writers/inheritance.js.map +1 -0
- package/dist/writers/interface.d.ts +16 -0
- package/dist/writers/interface.d.ts.map +1 -0
- package/dist/writers/interface.js +155 -0
- package/dist/writers/interface.js.map +1 -0
- package/dist/writers/method.d.ts +150 -0
- package/dist/writers/method.d.ts.map +1 -0
- package/dist/writers/method.js +510 -0
- package/dist/writers/method.js.map +1 -0
- package/dist/writers/param-classify.d.ts +93 -0
- package/dist/writers/param-classify.d.ts.map +1 -0
- package/dist/writers/param-classify.js +163 -0
- package/dist/writers/param-classify.js.map +1 -0
- package/dist/writers/property-accessor.d.ts +19 -0
- package/dist/writers/property-accessor.d.ts.map +1 -0
- package/dist/writers/property-accessor.js +117 -0
- package/dist/writers/property-accessor.js.map +1 -0
- package/dist/writers/registration.d.ts +27 -0
- package/dist/writers/registration.d.ts.map +1 -0
- package/dist/writers/registration.js +30 -0
- package/dist/writers/registration.js.map +1 -0
- package/dist/writers/return-wrap.d.ts +39 -0
- package/dist/writers/return-wrap.d.ts.map +1 -0
- package/dist/writers/return-wrap.js +152 -0
- package/dist/writers/return-wrap.js.map +1 -0
- package/dist/writers/runtime-override.d.ts +18 -0
- package/dist/writers/runtime-override.d.ts.map +1 -0
- package/dist/writers/runtime-override.js +44 -0
- package/dist/writers/runtime-override.js.map +1 -0
- package/dist/writers/signal.d.ts +21 -0
- package/dist/writers/signal.d.ts.map +1 -0
- package/dist/writers/signal.js +260 -0
- package/dist/writers/signal.js.map +1 -0
- package/dist/writers/ts-type.d.ts +50 -0
- package/dist/writers/ts-type.d.ts.map +1 -0
- package/dist/writers/ts-type.js +75 -0
- package/dist/writers/ts-type.js.map +1 -0
- package/dist/writers/types-ts.d.ts +27 -0
- package/dist/writers/types-ts.d.ts.map +1 -0
- package/dist/writers/types-ts.js +97 -0
- package/dist/writers/types-ts.js.map +1 -0
- package/dist/writers/value.d.ts +89 -0
- package/dist/writers/value.d.ts.map +1 -0
- package/dist/writers/value.js +402 -0
- package/dist/writers/value.js.map +1 -0
- package/package.json +55 -0
- package/src/dsl/context.ts +146 -0
- package/src/dsl/emit.ts +42 -0
- package/src/dsl/identifier.ts +52 -0
- package/src/dsl/imports.ts +109 -0
- package/src/dsl/module.ts +75 -0
- package/src/ffi/pipeline.ts +113 -0
- package/src/fingerprint.ts +50 -0
- package/src/gi-store.ts +199 -0
- package/src/gir/boxed.ts +94 -0
- package/src/gir/callback.ts +26 -0
- package/src/gir/class.ts +79 -0
- package/src/gir/enum.ts +55 -0
- package/src/gir/field.ts +40 -0
- package/src/gir/function.ts +67 -0
- package/src/gir/namespace.ts +118 -0
- package/src/gir/parameter.ts +120 -0
- package/src/gir/parse.ts +171 -0
- package/src/gir/primitives.ts +137 -0
- package/src/gir/property.ts +39 -0
- package/src/gir/qualified-name.ts +55 -0
- package/src/gir/qualify.ts +64 -0
- package/src/gir/repository.ts +149 -0
- package/src/gir/signal.ts +36 -0
- package/src/gir/size.ts +129 -0
- package/src/gir/type-ref.ts +180 -0
- package/src/index.ts +16 -0
- package/src/jsx-store.ts +53 -0
- package/src/react/compounds-meta.ts +99 -0
- package/src/react/compounds.ts +204 -0
- package/src/react/internal.ts +91 -0
- package/src/react/jsx.ts +155 -0
- package/src/react/pipeline.ts +29 -0
- package/src/react/props.ts +307 -0
- package/src/react/slot-props.ts +57 -0
- package/src/react/widgets.ts +185 -0
- package/src/runner.ts +89 -0
- package/src/store-fs.ts +157 -0
- package/src/transpile.ts +80 -0
- package/src/typecheck-store.ts +136 -0
- package/src/writers/alias.ts +19 -0
- package/src/writers/async.ts +44 -0
- package/src/writers/boxed-constructor.ts +105 -0
- package/src/writers/boxed-field-accessor.ts +306 -0
- package/src/writers/boxed-layout.ts +166 -0
- package/src/writers/boxed.ts +77 -0
- package/src/writers/callables.ts +339 -0
- package/src/writers/callback.ts +40 -0
- package/src/writers/class-struct-record.ts +75 -0
- package/src/writers/class-struct.ts +96 -0
- package/src/writers/class.ts +384 -0
- package/src/writers/constant.ts +29 -0
- package/src/writers/constructor-props.ts +130 -0
- package/src/writers/enum.ts +53 -0
- package/src/writers/function.ts +136 -0
- package/src/writers/gtype-binding.ts +48 -0
- package/src/writers/inheritance.ts +104 -0
- package/src/writers/interface.ts +168 -0
- package/src/writers/method.ts +678 -0
- package/src/writers/param-classify.ts +178 -0
- package/src/writers/property-accessor.ts +139 -0
- package/src/writers/registration.ts +42 -0
- package/src/writers/return-wrap.ts +180 -0
- package/src/writers/runtime-override.ts +59 -0
- package/src/writers/signal.ts +318 -0
- package/src/writers/ts-type.ts +115 -0
- package/src/writers/value.ts +485 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { toCamelCase, toIdentifier, toPascalCase } from "@gtkx/utils";
|
|
2
|
+
import type { GirClass } from "../gir/class.js";
|
|
3
|
+
import type { GirNamespace } from "../gir/namespace.js";
|
|
4
|
+
import { type GirParameter, isInoutParameter, isOutParameter } from "../gir/parameter.js";
|
|
5
|
+
import type { GirProperty } from "../gir/property.js";
|
|
6
|
+
import { resolveQualifiedClass, splitQualifiedName } from "../gir/qualified-name.js";
|
|
7
|
+
import { qualifyTypeRef } from "../gir/qualify.js";
|
|
8
|
+
import type { GirRepository } from "../gir/repository.js";
|
|
9
|
+
import type { GirSignal } from "../gir/signal.js";
|
|
10
|
+
import type { GirTypeRef, NamedTypeRef } from "../gir/type-ref.js";
|
|
11
|
+
import { renderBaseTypeFor, type TsTypeTarget } from "../writers/ts-type.js";
|
|
12
|
+
import { isScalarRef } from "../writers/value.js";
|
|
13
|
+
import { signalHandlerName } from "./widgets.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Rendered prop entries for a single widget's Props interface, together
|
|
17
|
+
* with imports the writer must request to keep type references valid.
|
|
18
|
+
*/
|
|
19
|
+
export type WidgetPropsEntries = {
|
|
20
|
+
/** Rendered prop lines, indented one level inside the interface body. */
|
|
21
|
+
readonly propLines: readonly string[];
|
|
22
|
+
/** Cross-namespace imports the writer must add to the surrounding module. */
|
|
23
|
+
readonly imports: ReadonlyMap<string, string>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Options for {@link buildWidgetPropsEntries}.
|
|
28
|
+
*/
|
|
29
|
+
export type WidgetPropsOptions = {
|
|
30
|
+
/** The full GIR repository. */
|
|
31
|
+
readonly repository: GirRepository;
|
|
32
|
+
/** The widget class whose props bag is being built. */
|
|
33
|
+
readonly klass: GirClass;
|
|
34
|
+
/** Property names that should be widened to `ReactNode` slot children. */
|
|
35
|
+
readonly slotPropNames?: ReadonlySet<string>;
|
|
36
|
+
/** Returns `true` when `candidate` already has its own widget Props interface. */
|
|
37
|
+
readonly isWidgetAncestor?: (candidate: GirClass) => boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type SignalRenderOptions = {
|
|
41
|
+
readonly repository: GirRepository;
|
|
42
|
+
readonly signal: GirSignal;
|
|
43
|
+
readonly imports: Map<string, string>;
|
|
44
|
+
readonly selfType: string;
|
|
45
|
+
readonly owningNamespace: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type ParentRef = { readonly klass: GirClass; readonly namespaceName: string };
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Builds a typed prop bag for one widget intrinsic.
|
|
52
|
+
*
|
|
53
|
+
* Combines the widget's own properties + signal handlers with those declared
|
|
54
|
+
* by its directly-implemented interfaces and by every non-widget ancestor up
|
|
55
|
+
* to the first widget parent (whose props are inherited via `extends`).
|
|
56
|
+
* Property types are written as TypeScript surface types resolved against the
|
|
57
|
+
* loaded GIR repository.
|
|
58
|
+
*
|
|
59
|
+
* @param options - {@link WidgetPropsOptions}
|
|
60
|
+
*/
|
|
61
|
+
export const buildWidgetPropsEntries = (options: WidgetPropsOptions): WidgetPropsEntries => {
|
|
62
|
+
const { repository, klass, slotPropNames = new Set<string>(), isWidgetAncestor = () => false } = options;
|
|
63
|
+
const imports = new Map<string, string>();
|
|
64
|
+
const propEntries: string[] = [];
|
|
65
|
+
const seen = new Set<string>();
|
|
66
|
+
|
|
67
|
+
const ownerName = klass.glibTypeName ?? klass.cType ?? klass.name;
|
|
68
|
+
const selfNamespace = currentNamespaceKey(repository, klass);
|
|
69
|
+
const selfTypeName = resolveSelfTypeName(klass, selfNamespace, imports);
|
|
70
|
+
|
|
71
|
+
const acceptProperty = (property: GirProperty, owningNamespace: string): void => {
|
|
72
|
+
const jsName = toIdentifier(toCamelCase(property.name));
|
|
73
|
+
if (seen.has(jsName)) return;
|
|
74
|
+
seen.add(jsName);
|
|
75
|
+
if (isPropOverridden(ownerName, jsName)) return;
|
|
76
|
+
if (slotPropNames.has(jsName)) {
|
|
77
|
+
propEntries.push(`${jsName}?: ReactNode | null;`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const qualified = qualifyTypeRef(property.type, owningNamespace);
|
|
81
|
+
const tsType = renderReactPropType(repository, qualified, true, imports);
|
|
82
|
+
propEntries.push(`${jsName}?: ${tsType} | null;`);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const acceptSignal = (signal: GirSignal, owningNamespace: string): void => {
|
|
86
|
+
const handlerName = signalHandlerName(signal.name);
|
|
87
|
+
if (seen.has(handlerName)) return;
|
|
88
|
+
seen.add(handlerName);
|
|
89
|
+
if (isSignalOverridden(ownerName, handlerName)) return;
|
|
90
|
+
const signature = renderSignalHandler({
|
|
91
|
+
repository,
|
|
92
|
+
signal,
|
|
93
|
+
imports,
|
|
94
|
+
selfType: selfTypeName,
|
|
95
|
+
owningNamespace,
|
|
96
|
+
});
|
|
97
|
+
propEntries.push(`${handlerName}?: ${signature};`);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
visitClassAndAncestors({ repository, klass, selfNamespace, isWidgetAncestor }, acceptProperty, acceptSignal);
|
|
101
|
+
|
|
102
|
+
return { propLines: propEntries, imports };
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const resolveSelfTypeName = (klass: GirClass, selfNamespace: string, imports: Map<string, string>): string => {
|
|
106
|
+
if (selfNamespace === "?") return toPascalCase(klass.name);
|
|
107
|
+
imports.set(selfNamespace, selfNamespace);
|
|
108
|
+
return `${selfNamespace}.${toPascalCase(klass.name)}`;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
type WalkContext = {
|
|
112
|
+
readonly repository: GirRepository;
|
|
113
|
+
readonly klass: GirClass;
|
|
114
|
+
readonly selfNamespace: string;
|
|
115
|
+
readonly isWidgetAncestor: (candidate: GirClass) => boolean;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const visitClassAndAncestors = (
|
|
119
|
+
walk: WalkContext,
|
|
120
|
+
acceptProperty: (property: GirProperty, owningNamespace: string) => void,
|
|
121
|
+
acceptSignal: (signal: GirSignal, owningNamespace: string) => void,
|
|
122
|
+
): void => {
|
|
123
|
+
const visit = (current: GirClass, owningNamespace: string): void => {
|
|
124
|
+
for (const property of current.properties) acceptProperty(property, owningNamespace);
|
|
125
|
+
for (const signal of current.signals) acceptSignal(signal, owningNamespace);
|
|
126
|
+
for (const implementsName of current.implements) {
|
|
127
|
+
const resolved = resolveInterface(walk.repository, current, implementsName);
|
|
128
|
+
if (resolved === undefined) continue;
|
|
129
|
+
for (const property of resolved.value.properties) acceptProperty(property, resolved.namespace.name);
|
|
130
|
+
for (const signal of resolved.value.signals) acceptSignal(signal, resolved.namespace.name);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
visit(walk.klass, walk.selfNamespace);
|
|
135
|
+
const visited = new Set<string>([`${walk.selfNamespace}.${walk.klass.name}`]);
|
|
136
|
+
let parentRef = resolveParent(walk.repository, walk.klass, walk.selfNamespace);
|
|
137
|
+
while (parentRef !== undefined && !walk.isWidgetAncestor(parentRef.klass)) {
|
|
138
|
+
const key = `${parentRef.namespaceName}.${parentRef.klass.name}`;
|
|
139
|
+
if (visited.has(key)) break;
|
|
140
|
+
visited.add(key);
|
|
141
|
+
visit(parentRef.klass, parentRef.namespaceName);
|
|
142
|
+
parentRef = resolveParent(walk.repository, parentRef.klass, parentRef.namespaceName);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const resolveParent = (repository: GirRepository, klass: GirClass, defaultNamespace: string): ParentRef | undefined => {
|
|
147
|
+
if (klass.parent === undefined) return undefined;
|
|
148
|
+
return resolveQualifiedClass(repository, klass.parent, defaultNamespace);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const currentNamespaceKey = (repository: GirRepository, klass: GirClass): string => {
|
|
152
|
+
for (const namespace of repository.namespaces.values()) {
|
|
153
|
+
if (namespace.classes.includes(klass) || namespace.interfaces.includes(klass)) {
|
|
154
|
+
return namespace.name;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return "?";
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const resolveInterface = (
|
|
161
|
+
repository: GirRepository,
|
|
162
|
+
declaringClass: GirClass,
|
|
163
|
+
implementsName: string,
|
|
164
|
+
): { readonly namespace: GirNamespace; readonly value: GirClass } | undefined => {
|
|
165
|
+
const { namespaceName, typeName } = splitQualifiedName(
|
|
166
|
+
implementsName,
|
|
167
|
+
currentNamespaceKey(repository, declaringClass),
|
|
168
|
+
);
|
|
169
|
+
const resolved = repository.resolveNamed(namespaceName, typeName);
|
|
170
|
+
if (resolved === undefined) return undefined;
|
|
171
|
+
if (resolved.kind !== "interface") return undefined;
|
|
172
|
+
return { namespace: resolved.namespace, value: resolved.value };
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Per-widget prop names that the React package re-types in a hand-written
|
|
177
|
+
* `declare module` augmentation. Skipping them in the generated bag
|
|
178
|
+
* prevents TS2717 "subsequent property declarations must have the same
|
|
179
|
+
* type" when the augmentation widens the GIR property to a declarative
|
|
180
|
+
* React shape (e.g. an array of child-props instead of a raw GObject
|
|
181
|
+
* model).
|
|
182
|
+
*
|
|
183
|
+
* Entries are matched against the widget's GLib type name (e.g.
|
|
184
|
+
* `"AdwToggleGroup"`); values are the JS prop names to skip.
|
|
185
|
+
*/
|
|
186
|
+
const PROP_OVERRIDES_BY_WIDGET: Readonly<Record<string, ReadonlySet<string>>> = {
|
|
187
|
+
AdwToggleGroup: new Set(["toggles"]),
|
|
188
|
+
AdwAlertDialog: new Set(["responses"]),
|
|
189
|
+
GtkColumnView: new Set(["columns"]),
|
|
190
|
+
GtkScale: new Set(["marks"]),
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const isPropOverridden = (ownerName: string, propName: string): boolean => {
|
|
194
|
+
const overrides = PROP_OVERRIDES_BY_WIDGET[ownerName];
|
|
195
|
+
return overrides?.has(propName) ?? false;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Per-widget signal-handler names whose generated emission is skipped so
|
|
200
|
+
* a hand-written `declare module` augmentation can supply a refined
|
|
201
|
+
* signature without triggering TS2717.
|
|
202
|
+
*/
|
|
203
|
+
const SIGNAL_OVERRIDES_BY_WIDGET: Readonly<Record<string, ReadonlySet<string>>> = {
|
|
204
|
+
GtkRange: new Set(["onValueChanged"]),
|
|
205
|
+
GtkScale: new Set(["onValueChanged"]),
|
|
206
|
+
GtkScaleButton: new Set(["onValueChanged"]),
|
|
207
|
+
GtkSpinButton: new Set(["onValueChanged"]),
|
|
208
|
+
AdwSpinRow: new Set(["onValueChanged"]),
|
|
209
|
+
AdwSwitchRow: new Set(["onActivated"]),
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const isSignalOverridden = (ownerName: string, handlerName: string): boolean => {
|
|
213
|
+
const overrides = SIGNAL_OVERRIDES_BY_WIDGET[ownerName];
|
|
214
|
+
return overrides?.has(handlerName) ?? false;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const renderSignalHandler = (options: SignalRenderOptions): string => {
|
|
218
|
+
const { repository, signal, imports, selfType, owningNamespace } = options;
|
|
219
|
+
const visible = signal.parameters.filter((parameter) => !parameter.isVarargs);
|
|
220
|
+
const params = visible
|
|
221
|
+
.filter((parameter) => !isOutParameter(parameter))
|
|
222
|
+
.map((parameter, index) => {
|
|
223
|
+
const name = parameter.name.length === 0 ? `arg${index + 1}` : toIdentifier(toCamelCase(parameter.name));
|
|
224
|
+
const qualified = qualifyTypeRef(parameter.type, owningNamespace);
|
|
225
|
+
const baseType = renderReactPropType(repository, qualified, false, imports);
|
|
226
|
+
return `${name}: ${baseType}`;
|
|
227
|
+
});
|
|
228
|
+
params.push(`self: ${selfType}`);
|
|
229
|
+
return `(${params.join(", ")}) => ${renderSignalReturnType(options, visible)}`;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Computes a signal handler's return type.
|
|
234
|
+
*
|
|
235
|
+
* Pure out-parameters (`direction="out"`, not caller-allocated) are surfaced
|
|
236
|
+
* through the return value as a tuple `[primary, ...outs]`, mirroring the
|
|
237
|
+
* convention {@link renderMethodReturnType} produces for methods: a value return
|
|
238
|
+
* with no outs stays scalar (with `| void` so handlers may opt out); a void
|
|
239
|
+
* return with a single out unwraps to that out's type; otherwise the primary
|
|
240
|
+
* (when present) leads a tuple of the out types.
|
|
241
|
+
*
|
|
242
|
+
* @param options - The signal render options
|
|
243
|
+
* @param visible - The signal's non-varargs parameters
|
|
244
|
+
*/
|
|
245
|
+
const renderSignalReturnType = (options: SignalRenderOptions, visible: readonly GirParameter[]): string => {
|
|
246
|
+
const { repository, signal, imports, owningNamespace } = options;
|
|
247
|
+
const qualifiedReturn = qualifyTypeRef(signal.returnValue.type, owningNamespace);
|
|
248
|
+
const baseReturn = renderReactPropType(repository, qualifiedReturn, signal.returnValue.nullable, imports);
|
|
249
|
+
const outTypes = visible
|
|
250
|
+
.filter(
|
|
251
|
+
(parameter) =>
|
|
252
|
+
isOutParameter(parameter) ||
|
|
253
|
+
(isInoutParameter(parameter) && isScalarRef(repository, owningNamespace, parameter.type)),
|
|
254
|
+
)
|
|
255
|
+
.map((parameter) =>
|
|
256
|
+
renderReactPropType(repository, qualifyTypeRef(parameter.type, owningNamespace), false, imports),
|
|
257
|
+
);
|
|
258
|
+
if (outTypes.length === 0) {
|
|
259
|
+
return baseReturn === "void" ? "void" : `${baseReturn} | void`;
|
|
260
|
+
}
|
|
261
|
+
if (baseReturn !== "void") {
|
|
262
|
+
return `[${baseReturn}, ${outTypes.join(", ")}]`;
|
|
263
|
+
}
|
|
264
|
+
const [single, ...rest] = outTypes;
|
|
265
|
+
if (rest.length === 0 && single !== undefined) return single;
|
|
266
|
+
return `[${outTypes.join(", ")}]`;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const reactTarget = (repository: GirRepository, imports: Map<string, string>): TsTypeTarget => ({
|
|
270
|
+
containerStyle: "record",
|
|
271
|
+
callbackType: "(...args: unknown[]) => unknown",
|
|
272
|
+
byteArrayAsNumber: false,
|
|
273
|
+
renderNamed: (ref) => namedTsType(repository, ref, imports),
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const renderReactPropType = (
|
|
277
|
+
repository: GirRepository,
|
|
278
|
+
ref: GirTypeRef | undefined,
|
|
279
|
+
isNullable: boolean,
|
|
280
|
+
imports: Map<string, string>,
|
|
281
|
+
): string => {
|
|
282
|
+
const base = renderBaseTypeFor(reactTarget(repository, imports), ref);
|
|
283
|
+
return isNullable ? `${base} | null` : base;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const namedTsType = (repository: GirRepository, ref: NamedTypeRef, imports: Map<string, string>): string => {
|
|
287
|
+
const namespaceName = ref.namespaceName;
|
|
288
|
+
if (namespaceName === undefined) {
|
|
289
|
+
return ref.typeName;
|
|
290
|
+
}
|
|
291
|
+
const resolved = repository.resolveNamed(namespaceName, ref.typeName);
|
|
292
|
+
if (resolved === undefined) {
|
|
293
|
+
imports.set(namespaceName, namespaceName);
|
|
294
|
+
return `${namespaceName}.${ref.typeName}`;
|
|
295
|
+
}
|
|
296
|
+
if (resolved.kind === "callback") return "(...args: unknown[]) => unknown";
|
|
297
|
+
if (resolved.kind === "alias") {
|
|
298
|
+
if (resolved.target === undefined) return "number";
|
|
299
|
+
return namedTsType(
|
|
300
|
+
repository,
|
|
301
|
+
{ kind: "named", namespaceName: resolved.namespace.name, typeName: resolved.target, cType: undefined },
|
|
302
|
+
imports,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
imports.set(namespaceName, namespaceName);
|
|
306
|
+
return `${namespaceName}.${ref.typeName}`;
|
|
307
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in widget-typed properties that should be exposed as JSX child
|
|
3
|
+
* slots (typed as `ReactNode`) rather than as raw widget refs.
|
|
4
|
+
*
|
|
5
|
+
* The keys are JSX element names (i.e. GLib type names such as
|
|
6
|
+
* `"AdwWindow"`, `"GtkHeaderBar"`); the values are the camelCase property
|
|
7
|
+
* names that should be promoted. User-supplied `slotProps` from
|
|
8
|
+
* `gtkx.config.ts` merge into this map.
|
|
9
|
+
*/
|
|
10
|
+
const BUILT_IN_SLOT_PROPS: Readonly<Record<string, readonly string[]>> = Object.freeze({
|
|
11
|
+
GtkWindow: ["titlebar"],
|
|
12
|
+
AdwWindow: ["content"],
|
|
13
|
+
AdwApplicationWindow: ["content"],
|
|
14
|
+
AdwAlertDialog: ["extraChild"],
|
|
15
|
+
AdwBottomSheet: ["bottomBar", "content", "sheet"],
|
|
16
|
+
GtkCenterBox: ["centerWidget", "endWidget", "startWidget"],
|
|
17
|
+
GtkExpander: ["labelWidget"],
|
|
18
|
+
AdwFlap: ["content", "flap", "separator"],
|
|
19
|
+
GtkFrame: ["labelWidget"],
|
|
20
|
+
GtkHeaderBar: ["titleWidget"],
|
|
21
|
+
AdwHeaderBar: ["titleWidget"],
|
|
22
|
+
GtkMenuButton: ["popover"],
|
|
23
|
+
AdwMessageDialog: ["extraChild"],
|
|
24
|
+
AdwNavigationSplitView: ["content", "sidebar"],
|
|
25
|
+
AdwOverlaySplitView: ["content", "sidebar"],
|
|
26
|
+
GtkPaned: ["endChild", "startChild"],
|
|
27
|
+
AdwPreferencesGroup: ["headerSuffix"],
|
|
28
|
+
AdwPreferencesPage: ["banner"],
|
|
29
|
+
AdwSplitButton: ["popover"],
|
|
30
|
+
AdwTabBar: ["endActionWidget", "startActionWidget"],
|
|
31
|
+
AdwToolbarView: ["content"],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Merges built-in slot props with the user-supplied `slotProps` map.
|
|
36
|
+
*
|
|
37
|
+
* Per JSX element name, the resulting list is the union of both sources
|
|
38
|
+
* with duplicates removed and a stable alphabetical sort.
|
|
39
|
+
*
|
|
40
|
+
* @param userSlotProps - The user-provided `slotProps` map, or `undefined`
|
|
41
|
+
*/
|
|
42
|
+
export const mergeSlotProps = (
|
|
43
|
+
userSlotProps: Readonly<Record<string, readonly string[]>> | undefined,
|
|
44
|
+
): Readonly<Record<string, readonly string[]>> => {
|
|
45
|
+
const result: Record<string, string[]> = {};
|
|
46
|
+
for (const [key, values] of Object.entries(BUILT_IN_SLOT_PROPS)) {
|
|
47
|
+
result[key] = [...values];
|
|
48
|
+
}
|
|
49
|
+
if (userSlotProps !== undefined) {
|
|
50
|
+
for (const [key, values] of Object.entries(userSlotProps)) {
|
|
51
|
+
const merged = new Set<string>(result[key] ?? []);
|
|
52
|
+
for (const value of values) merged.add(value);
|
|
53
|
+
result[key] = [...merged].sort((a, b) => a.localeCompare(b));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { toCamelCase, toIdentifier, toUpperFirst } from "@gtkx/utils";
|
|
2
|
+
import type { GirClass } from "../gir/class.js";
|
|
3
|
+
import type { GirNamespace } from "../gir/namespace.js";
|
|
4
|
+
import { splitQualifiedName } from "../gir/qualified-name.js";
|
|
5
|
+
import type { GirRepository } from "../gir/repository.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Maps a kebab-case signal name to its `on`-prefixed camelCase handler prop
|
|
9
|
+
* name (e.g. `value-changed` → `onValueChanged`).
|
|
10
|
+
*
|
|
11
|
+
* @param signalName - The GIR signal name
|
|
12
|
+
*/
|
|
13
|
+
export const signalHandlerName = (signalName: string): string =>
|
|
14
|
+
`on${toUpperFirst(toIdentifier(toCamelCase(signalName)))}`;
|
|
15
|
+
|
|
16
|
+
/** A class qualified by its declaring namespace and the GLib type name it exposes. */
|
|
17
|
+
export type WidgetCandidate = {
|
|
18
|
+
readonly glibName: string;
|
|
19
|
+
readonly klass: GirClass;
|
|
20
|
+
readonly namespace: GirNamespace;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Iterates every `(class, namespace, glibName)` triple in the repository whose
|
|
25
|
+
* `glib:type-name` (or C type fallback) is defined.
|
|
26
|
+
*
|
|
27
|
+
* The order follows GIR file order within a namespace and `namespaces` map
|
|
28
|
+
* iteration order between namespaces; callers that need stable output should
|
|
29
|
+
* sort the result themselves.
|
|
30
|
+
*
|
|
31
|
+
* @param repository - The repository to walk
|
|
32
|
+
*/
|
|
33
|
+
export function* iterateClassesWithGlibName(repository: GirRepository): IterableIterator<WidgetCandidate> {
|
|
34
|
+
for (const namespace of repository.namespaces.values()) {
|
|
35
|
+
for (const klass of namespace.classes) {
|
|
36
|
+
const glibName = klass.glibTypeName ?? klass.cType;
|
|
37
|
+
if (glibName === undefined) continue;
|
|
38
|
+
yield { glibName, klass, namespace };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const resolveParentClass = (
|
|
44
|
+
repository: GirRepository,
|
|
45
|
+
parent: string,
|
|
46
|
+
namespace: GirNamespace,
|
|
47
|
+
): { readonly klass: GirClass; readonly namespace: GirNamespace } | undefined => {
|
|
48
|
+
const { namespaceName, typeName } = splitQualifiedName(parent, namespace.name);
|
|
49
|
+
const resolved = repository.resolveNamed(namespaceName, typeName);
|
|
50
|
+
if (resolved === undefined || (resolved.kind !== "class" && resolved.kind !== "interface")) return undefined;
|
|
51
|
+
return { klass: resolved.value, namespace: resolved.namespace };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** A resolved interface qualified by the namespace that declares it. */
|
|
55
|
+
export type ResolvedQualifiedInterface = { readonly klass: GirClass; readonly namespace: GirNamespace };
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolves every interface a class implements, including the transitive
|
|
59
|
+
* prerequisite interfaces of those interfaces, nearest first.
|
|
60
|
+
*
|
|
61
|
+
* Interfaces never appear in the GObject type-parent chain, so the React
|
|
62
|
+
* runtime cannot reach an interface's signals or properties by walking a
|
|
63
|
+
* widget's ancestors. Each implementing class must therefore carry its
|
|
64
|
+
* interfaces' metadata directly. Prerequisite interfaces (e.g. a
|
|
65
|
+
* `Gtk.SelectionModel`'s `Gio.ListModel`) are folded in transitively; class
|
|
66
|
+
* prerequisites are ignored because their metadata is reachable through the
|
|
67
|
+
* type-parent chain.
|
|
68
|
+
*
|
|
69
|
+
* @param klass - The class whose interfaces to resolve
|
|
70
|
+
* @param namespace - The namespace the class lives in
|
|
71
|
+
* @param repository - The repository for cross-namespace lookups
|
|
72
|
+
*/
|
|
73
|
+
export const implementedInterfaces = (
|
|
74
|
+
klass: GirClass,
|
|
75
|
+
namespace: GirNamespace,
|
|
76
|
+
repository: GirRepository,
|
|
77
|
+
): readonly ResolvedQualifiedInterface[] => {
|
|
78
|
+
const result: ResolvedQualifiedInterface[] = [];
|
|
79
|
+
const visited = new Set<string>();
|
|
80
|
+
const visit = (names: readonly string[], fromNamespace: GirNamespace): void => {
|
|
81
|
+
for (const name of names) {
|
|
82
|
+
const { namespaceName, typeName } = splitQualifiedName(name, fromNamespace.name);
|
|
83
|
+
const resolved = repository.resolveNamed(namespaceName, typeName);
|
|
84
|
+
if (resolved === undefined || resolved.kind !== "interface") continue;
|
|
85
|
+
const key = `${resolved.namespace.name}.${resolved.value.name}`;
|
|
86
|
+
if (visited.has(key)) continue;
|
|
87
|
+
visited.add(key);
|
|
88
|
+
result.push({ klass: resolved.value, namespace: resolved.namespace });
|
|
89
|
+
visit(resolved.value.prerequisites, resolved.namespace);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
visit(klass.implements, namespace);
|
|
93
|
+
return result;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Returns the GLib type names of a class and every ancestor it descends from,
|
|
98
|
+
* nearest first. Used to let a subclass inherit slot-prop declarations made on
|
|
99
|
+
* its ancestors.
|
|
100
|
+
*
|
|
101
|
+
* @param klass - The class to start from
|
|
102
|
+
* @param namespace - The namespace the class lives in
|
|
103
|
+
* @param repository - The repository for cross-namespace parent lookups
|
|
104
|
+
*/
|
|
105
|
+
export const ancestorGlibNames = (
|
|
106
|
+
klass: GirClass,
|
|
107
|
+
namespace: GirNamespace,
|
|
108
|
+
repository: GirRepository,
|
|
109
|
+
): readonly string[] => {
|
|
110
|
+
const names: string[] = [];
|
|
111
|
+
let currentClass: GirClass | undefined = klass;
|
|
112
|
+
let currentNamespace: GirNamespace | undefined = namespace;
|
|
113
|
+
while (currentClass !== undefined && currentNamespace !== undefined) {
|
|
114
|
+
const glibName = currentClass.glibTypeName ?? currentClass.cType;
|
|
115
|
+
if (glibName !== undefined) names.push(glibName);
|
|
116
|
+
if (currentClass.parent === undefined) break;
|
|
117
|
+
const next = resolveParentClass(repository, currentClass.parent, currentNamespace);
|
|
118
|
+
if (next === undefined) break;
|
|
119
|
+
currentClass = next.klass;
|
|
120
|
+
currentNamespace = next.namespace;
|
|
121
|
+
}
|
|
122
|
+
return names;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const descendsFrom = (
|
|
126
|
+
klass: GirClass,
|
|
127
|
+
namespace: GirNamespace,
|
|
128
|
+
repository: GirRepository,
|
|
129
|
+
matches: (glibName: string) => boolean,
|
|
130
|
+
): boolean => {
|
|
131
|
+
let currentClass: GirClass | undefined = klass;
|
|
132
|
+
let currentNamespace: GirNamespace | undefined = namespace;
|
|
133
|
+
while (currentClass !== undefined && currentNamespace !== undefined) {
|
|
134
|
+
const glibName = currentClass.glibTypeName ?? currentClass.cType ?? "";
|
|
135
|
+
if (matches(glibName)) return true;
|
|
136
|
+
if (currentClass.parent === undefined) return false;
|
|
137
|
+
const next = resolveParentClass(repository, currentClass.parent, currentNamespace);
|
|
138
|
+
if (next === undefined) return false;
|
|
139
|
+
currentClass = next.klass;
|
|
140
|
+
currentNamespace = next.namespace;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Decides whether a `<class>` is a "widget" for the React reconciler — a
|
|
147
|
+
* descendant (direct or transitive) of `GtkWidget`.
|
|
148
|
+
*
|
|
149
|
+
* Walks the parent chain across namespaces. Returns `false` if any
|
|
150
|
+
* ancestor cannot be resolved (which is preferable to silently treating
|
|
151
|
+
* an unknown type as a widget).
|
|
152
|
+
*
|
|
153
|
+
* @param klass - The class to inspect
|
|
154
|
+
* @param namespace - The namespace the class lives in
|
|
155
|
+
* @param repository - The repository for cross-namespace parent lookups
|
|
156
|
+
*/
|
|
157
|
+
export const isWidgetClass = (klass: GirClass, namespace: GirNamespace, repository: GirRepository): boolean =>
|
|
158
|
+
descendsFrom(klass, namespace, repository, (glibName) => glibName === "GtkWidget");
|
|
159
|
+
|
|
160
|
+
const AUXILIARY_BASE_TYPES: ReadonlySet<string> = new Set(["GtkEventController", "GtkGesture", "GtkLayoutManager"]);
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Decides whether a `<class>` is a non-widget React reconciler node — an event
|
|
164
|
+
* controller, gesture, or layout manager (direct or transitive descendant of
|
|
165
|
+
* `GtkEventController`, `GtkGesture`, or `GtkLayoutManager`). These are JSX
|
|
166
|
+
* elements that need the same prop/signal metadata as widgets.
|
|
167
|
+
*
|
|
168
|
+
* @param klass - The class to inspect
|
|
169
|
+
* @param namespace - The namespace the class lives in
|
|
170
|
+
* @param repository - The repository for cross-namespace parent lookups
|
|
171
|
+
*/
|
|
172
|
+
const isAuxiliaryReactType = (klass: GirClass, namespace: GirNamespace, repository: GirRepository): boolean =>
|
|
173
|
+
descendsFrom(klass, namespace, repository, (glibName) => AUXILIARY_BASE_TYPES.has(glibName));
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Decides whether a `<class>` is any React reconciler node — a widget or one
|
|
177
|
+
* of the auxiliary node kinds. This is the set of classes that need generated
|
|
178
|
+
* prop, signal, and construct-only metadata.
|
|
179
|
+
*
|
|
180
|
+
* @param klass - The class to inspect
|
|
181
|
+
* @param namespace - The namespace the class lives in
|
|
182
|
+
* @param repository - The repository for cross-namespace parent lookups
|
|
183
|
+
*/
|
|
184
|
+
export const isReactNodeClass = (klass: GirClass, namespace: GirNamespace, repository: GirRepository): boolean =>
|
|
185
|
+
isWidgetClass(klass, namespace, repository) || isAuxiliaryReactType(klass, namespace, repository);
|
package/src/runner.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { generateNamespaceModule } from "./ffi/pipeline.js";
|
|
2
|
+
import { computeFingerprint } from "./fingerprint.js";
|
|
3
|
+
import { type GiNamespaceInput, type GiStoreOptions, writeGiStore } from "./gi-store.js";
|
|
4
|
+
import { loadGirRepository } from "./gir/repository.js";
|
|
5
|
+
import { type JsxStoreOptions, writeJsxStore } from "./jsx-store.js";
|
|
6
|
+
import { generateReactFiles } from "./react/pipeline.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Options for {@link CodegenRunner}.
|
|
10
|
+
*/
|
|
11
|
+
export type CodegenRunnerOptions = {
|
|
12
|
+
/** Resolved `Name-Version` namespace identifiers to generate. */
|
|
13
|
+
readonly libraries: readonly string[];
|
|
14
|
+
/** Directories searched for `.gir` files. */
|
|
15
|
+
readonly girPath: readonly string[];
|
|
16
|
+
/** Optional user slot-prop overrides keyed by JSX element name. */
|
|
17
|
+
readonly slotProps?: Readonly<Record<string, readonly string[]>>;
|
|
18
|
+
/** Target for the injected `@gtkx/gi` bindings package. */
|
|
19
|
+
readonly gi: GiStoreOptions;
|
|
20
|
+
/** Target for the injected `@gtkx/react-jsx` unit; React is skipped when omitted. */
|
|
21
|
+
readonly jsx?: JsxStoreOptions;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Result returned from {@link CodegenRunner.run}.
|
|
26
|
+
*/
|
|
27
|
+
export type CodegenRunnerResult = {
|
|
28
|
+
/** Number of FFI namespaces produced. */
|
|
29
|
+
readonly namespaces: number;
|
|
30
|
+
/** Number of widget intrinsics emitted into `jsx.ts`. */
|
|
31
|
+
readonly widgets: number;
|
|
32
|
+
/** Wall-clock duration in milliseconds. */
|
|
33
|
+
readonly duration: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Single-entry orchestrator for the GTKX codegen.
|
|
38
|
+
*
|
|
39
|
+
* Loads the GIR repository, runs the FFI pipeline per namespace, runs the
|
|
40
|
+
* React pipeline once, and materializes the self-contained `@gtkx/gi` (and
|
|
41
|
+
* optional `@gtkx/react-jsx`) packages into the project's `node_modules`.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { CodegenRunner } from "@gtkx/codegen";
|
|
46
|
+
*
|
|
47
|
+
* await new CodegenRunner({
|
|
48
|
+
* libraries: ["Gtk-4.0", "Adw-1"],
|
|
49
|
+
* girPath: ["/usr/share/gir-1.0"],
|
|
50
|
+
* gi: { storeDir, linkDir, realFfiDir, realNativeDir, version },
|
|
51
|
+
* }).run();
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export class CodegenRunner {
|
|
55
|
+
constructor(private readonly options: CodegenRunnerOptions) {}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Executes the full codegen pipeline and materializes the output packages.
|
|
59
|
+
*/
|
|
60
|
+
async run(): Promise<CodegenRunnerResult> {
|
|
61
|
+
const start = Date.now();
|
|
62
|
+
const repository = loadGirRepository(this.options.libraries, this.options.girPath);
|
|
63
|
+
|
|
64
|
+
const namespaces: GiNamespaceInput[] = [];
|
|
65
|
+
for (const namespace of repository.namespaces.values()) {
|
|
66
|
+
const { source } = generateNamespaceModule(namespace, repository);
|
|
67
|
+
namespaces.push({ directory: namespace.name.toLowerCase(), rawSource: source });
|
|
68
|
+
}
|
|
69
|
+
const libraries = [...this.options.libraries];
|
|
70
|
+
writeGiStore(this.options.gi, namespaces, {
|
|
71
|
+
value: computeFingerprint(repository.girFiles, libraries),
|
|
72
|
+
girFiles: repository.girFiles,
|
|
73
|
+
libraries,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
let widgetCount = 0;
|
|
77
|
+
if (this.options.jsx !== undefined) {
|
|
78
|
+
const reactPipeline = generateReactFiles(repository, this.options.slotProps);
|
|
79
|
+
writeJsxStore(this.options.jsx, reactPipeline.files);
|
|
80
|
+
widgetCount = reactPipeline.widgetCount;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
namespaces: repository.namespaces.size,
|
|
85
|
+
widgets: widgetCount,
|
|
86
|
+
duration: Date.now() - start,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|