@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.
Files changed (340) hide show
  1. package/LICENSE +373 -0
  2. package/dist/dsl/context.d.ts +104 -0
  3. package/dist/dsl/context.d.ts.map +1 -0
  4. package/dist/dsl/context.js +140 -0
  5. package/dist/dsl/context.js.map +1 -0
  6. package/dist/dsl/emit.d.ts +28 -0
  7. package/dist/dsl/emit.d.ts.map +1 -0
  8. package/dist/dsl/emit.js +41 -0
  9. package/dist/dsl/emit.js.map +1 -0
  10. package/dist/dsl/identifier.d.ts +28 -0
  11. package/dist/dsl/identifier.d.ts.map +1 -0
  12. package/dist/dsl/identifier.js +45 -0
  13. package/dist/dsl/identifier.js.map +1 -0
  14. package/dist/dsl/imports.d.ts +58 -0
  15. package/dist/dsl/imports.d.ts.map +1 -0
  16. package/dist/dsl/imports.js +107 -0
  17. package/dist/dsl/imports.js.map +1 -0
  18. package/dist/dsl/module.d.ts +53 -0
  19. package/dist/dsl/module.d.ts.map +1 -0
  20. package/dist/dsl/module.js +76 -0
  21. package/dist/dsl/module.js.map +1 -0
  22. package/dist/ffi/pipeline.d.ts +19 -0
  23. package/dist/ffi/pipeline.d.ts.map +1 -0
  24. package/dist/ffi/pipeline.js +107 -0
  25. package/dist/ffi/pipeline.js.map +1 -0
  26. package/dist/fingerprint.d.ts +30 -0
  27. package/dist/fingerprint.d.ts.map +1 -0
  28. package/dist/fingerprint.js +33 -0
  29. package/dist/fingerprint.js.map +1 -0
  30. package/dist/gi-store.d.ts +34 -0
  31. package/dist/gi-store.d.ts.map +1 -0
  32. package/dist/gi-store.js +154 -0
  33. package/dist/gi-store.js.map +1 -0
  34. package/dist/gir/boxed.d.ts +66 -0
  35. package/dist/gir/boxed.d.ts.map +1 -0
  36. package/dist/gir/boxed.js +49 -0
  37. package/dist/gir/boxed.js.map +1 -0
  38. package/dist/gir/callback.d.ts +15 -0
  39. package/dist/gir/callback.d.ts.map +1 -0
  40. package/dist/gir/callback.js +17 -0
  41. package/dist/gir/callback.js.map +1 -0
  42. package/dist/gir/class.d.ts +49 -0
  43. package/dist/gir/class.d.ts.map +1 -0
  44. package/dist/gir/class.js +42 -0
  45. package/dist/gir/class.js.map +1 -0
  46. package/dist/gir/enum.d.ts +39 -0
  47. package/dist/gir/enum.d.ts.map +1 -0
  48. package/dist/gir/enum.js +24 -0
  49. package/dist/gir/enum.js.map +1 -0
  50. package/dist/gir/field.d.ts +27 -0
  51. package/dist/gir/field.d.ts.map +1 -0
  52. package/dist/gir/field.js +19 -0
  53. package/dist/gir/field.js.map +1 -0
  54. package/dist/gir/function.d.ts +47 -0
  55. package/dist/gir/function.d.ts.map +1 -0
  56. package/dist/gir/function.js +28 -0
  57. package/dist/gir/function.js.map +1 -0
  58. package/dist/gir/namespace.d.ts +65 -0
  59. package/dist/gir/namespace.d.ts.map +1 -0
  60. package/dist/gir/namespace.js +60 -0
  61. package/dist/gir/namespace.js.map +1 -0
  62. package/dist/gir/parameter.d.ts +82 -0
  63. package/dist/gir/parameter.d.ts.map +1 -0
  64. package/dist/gir/parameter.js +66 -0
  65. package/dist/gir/parameter.js.map +1 -0
  66. package/dist/gir/parse.d.ts +78 -0
  67. package/dist/gir/parse.d.ts.map +1 -0
  68. package/dist/gir/parse.js +155 -0
  69. package/dist/gir/parse.js.map +1 -0
  70. package/dist/gir/primitives.d.ts +36 -0
  71. package/dist/gir/primitives.d.ts.map +1 -0
  72. package/dist/gir/primitives.js +109 -0
  73. package/dist/gir/primitives.js.map +1 -0
  74. package/dist/gir/property.d.ts +29 -0
  75. package/dist/gir/property.d.ts.map +1 -0
  76. package/dist/gir/property.js +16 -0
  77. package/dist/gir/property.js.map +1 -0
  78. package/dist/gir/qualified-name.d.ts +37 -0
  79. package/dist/gir/qualified-name.d.ts.map +1 -0
  80. package/dist/gir/qualified-name.js +39 -0
  81. package/dist/gir/qualified-name.js.map +1 -0
  82. package/dist/gir/qualify.d.ts +32 -0
  83. package/dist/gir/qualify.d.ts.map +1 -0
  84. package/dist/gir/qualify.js +60 -0
  85. package/dist/gir/qualify.js.map +1 -0
  86. package/dist/gir/repository.d.ts +82 -0
  87. package/dist/gir/repository.d.ts.map +1 -0
  88. package/dist/gir/repository.js +122 -0
  89. package/dist/gir/repository.js.map +1 -0
  90. package/dist/gir/signal.d.ts +22 -0
  91. package/dist/gir/signal.d.ts.map +1 -0
  92. package/dist/gir/signal.js +20 -0
  93. package/dist/gir/signal.js.map +1 -0
  94. package/dist/gir/size.d.ts +67 -0
  95. package/dist/gir/size.d.ts.map +1 -0
  96. package/dist/gir/size.js +87 -0
  97. package/dist/gir/size.js.map +1 -0
  98. package/dist/gir/type-ref.d.ts +79 -0
  99. package/dist/gir/type-ref.d.ts.map +1 -0
  100. package/dist/gir/type-ref.js +101 -0
  101. package/dist/gir/type-ref.js.map +1 -0
  102. package/dist/index.d.ts +12 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +12 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/jsx-store.d.ts +22 -0
  107. package/dist/jsx-store.d.ts.map +1 -0
  108. package/dist/jsx-store.js +40 -0
  109. package/dist/jsx-store.js.map +1 -0
  110. package/dist/react/compounds-meta.d.ts +18 -0
  111. package/dist/react/compounds-meta.d.ts.map +1 -0
  112. package/dist/react/compounds-meta.js +93 -0
  113. package/dist/react/compounds-meta.js.map +1 -0
  114. package/dist/react/compounds.d.ts +27 -0
  115. package/dist/react/compounds.d.ts.map +1 -0
  116. package/dist/react/compounds.js +164 -0
  117. package/dist/react/compounds.js.map +1 -0
  118. package/dist/react/internal.d.ts +14 -0
  119. package/dist/react/internal.d.ts.map +1 -0
  120. package/dist/react/internal.js +79 -0
  121. package/dist/react/internal.js.map +1 -0
  122. package/dist/react/jsx.d.ts +22 -0
  123. package/dist/react/jsx.d.ts.map +1 -0
  124. package/dist/react/jsx.js +134 -0
  125. package/dist/react/jsx.js.map +1 -0
  126. package/dist/react/pipeline.d.ts +13 -0
  127. package/dist/react/pipeline.d.ts.map +1 -0
  128. package/dist/react/pipeline.js +25 -0
  129. package/dist/react/pipeline.js.map +1 -0
  130. package/dist/react/props.d.ts +38 -0
  131. package/dist/react/props.d.ts.map +1 -0
  132. package/dist/react/props.js +231 -0
  133. package/dist/react/props.js.map +1 -0
  134. package/dist/react/slot-props.d.ts +10 -0
  135. package/dist/react/slot-props.d.ts.map +1 -0
  136. package/dist/react/slot-props.js +56 -0
  137. package/dist/react/slot-props.js.map +1 -0
  138. package/dist/react/widgets.d.ts +83 -0
  139. package/dist/react/widgets.d.ts.map +1 -0
  140. package/dist/react/widgets.js +152 -0
  141. package/dist/react/widgets.js.map +1 -0
  142. package/dist/runner.d.ts +55 -0
  143. package/dist/runner.d.ts.map +1 -0
  144. package/dist/runner.js +60 -0
  145. package/dist/runner.js.map +1 -0
  146. package/dist/store-fs.d.ts +109 -0
  147. package/dist/store-fs.d.ts.map +1 -0
  148. package/dist/store-fs.js +98 -0
  149. package/dist/store-fs.js.map +1 -0
  150. package/dist/transpile.d.ts +32 -0
  151. package/dist/transpile.d.ts.map +1 -0
  152. package/dist/transpile.js +63 -0
  153. package/dist/transpile.js.map +1 -0
  154. package/dist/typecheck-store.d.ts +41 -0
  155. package/dist/typecheck-store.d.ts.map +1 -0
  156. package/dist/typecheck-store.js +102 -0
  157. package/dist/typecheck-store.js.map +1 -0
  158. package/dist/writers/alias.d.ts +13 -0
  159. package/dist/writers/alias.d.ts.map +1 -0
  160. package/dist/writers/alias.js +17 -0
  161. package/dist/writers/alias.js.map +1 -0
  162. package/dist/writers/async.d.ts +22 -0
  163. package/dist/writers/async.d.ts.map +1 -0
  164. package/dist/writers/async.js +48 -0
  165. package/dist/writers/async.js.map +1 -0
  166. package/dist/writers/boxed-constructor.d.ts +26 -0
  167. package/dist/writers/boxed-constructor.d.ts.map +1 -0
  168. package/dist/writers/boxed-constructor.js +85 -0
  169. package/dist/writers/boxed-constructor.js.map +1 -0
  170. package/dist/writers/boxed-field-accessor.d.ts +18 -0
  171. package/dist/writers/boxed-field-accessor.d.ts.map +1 -0
  172. package/dist/writers/boxed-field-accessor.js +260 -0
  173. package/dist/writers/boxed-field-accessor.js.map +1 -0
  174. package/dist/writers/boxed-layout.d.ts +27 -0
  175. package/dist/writers/boxed-layout.d.ts.map +1 -0
  176. package/dist/writers/boxed-layout.js +128 -0
  177. package/dist/writers/boxed-layout.js.map +1 -0
  178. package/dist/writers/boxed.d.ts +20 -0
  179. package/dist/writers/boxed.d.ts.map +1 -0
  180. package/dist/writers/boxed.js +71 -0
  181. package/dist/writers/boxed.js.map +1 -0
  182. package/dist/writers/callables.d.ts +124 -0
  183. package/dist/writers/callables.d.ts.map +1 -0
  184. package/dist/writers/callables.js +283 -0
  185. package/dist/writers/callables.js.map +1 -0
  186. package/dist/writers/callback.d.ts +16 -0
  187. package/dist/writers/callback.d.ts.map +1 -0
  188. package/dist/writers/callback.js +38 -0
  189. package/dist/writers/callback.js.map +1 -0
  190. package/dist/writers/class-struct-record.d.ts +31 -0
  191. package/dist/writers/class-struct-record.d.ts.map +1 -0
  192. package/dist/writers/class-struct-record.js +67 -0
  193. package/dist/writers/class-struct-record.js.map +1 -0
  194. package/dist/writers/class-struct.d.ts +22 -0
  195. package/dist/writers/class-struct.d.ts.map +1 -0
  196. package/dist/writers/class-struct.js +85 -0
  197. package/dist/writers/class-struct.js.map +1 -0
  198. package/dist/writers/class.d.ts +16 -0
  199. package/dist/writers/class.d.ts.map +1 -0
  200. package/dist/writers/class.js +307 -0
  201. package/dist/writers/class.js.map +1 -0
  202. package/dist/writers/constant.d.ts +15 -0
  203. package/dist/writers/constant.d.ts.map +1 -0
  204. package/dist/writers/constant.js +23 -0
  205. package/dist/writers/constant.js.map +1 -0
  206. package/dist/writers/constants.d.ts +15 -0
  207. package/dist/writers/constants.d.ts.map +1 -0
  208. package/dist/writers/constants.js +23 -0
  209. package/dist/writers/constants.js.map +1 -0
  210. package/dist/writers/constructor-props.d.ts +32 -0
  211. package/dist/writers/constructor-props.d.ts.map +1 -0
  212. package/dist/writers/constructor-props.js +110 -0
  213. package/dist/writers/constructor-props.js.map +1 -0
  214. package/dist/writers/enum.d.ts +21 -0
  215. package/dist/writers/enum.d.ts.map +1 -0
  216. package/dist/writers/enum.js +48 -0
  217. package/dist/writers/enum.js.map +1 -0
  218. package/dist/writers/function.d.ts +40 -0
  219. package/dist/writers/function.d.ts.map +1 -0
  220. package/dist/writers/function.js +127 -0
  221. package/dist/writers/function.js.map +1 -0
  222. package/dist/writers/gtype-binding.d.ts +19 -0
  223. package/dist/writers/gtype-binding.d.ts.map +1 -0
  224. package/dist/writers/gtype-binding.js +40 -0
  225. package/dist/writers/gtype-binding.js.map +1 -0
  226. package/dist/writers/inheritance.d.ts +48 -0
  227. package/dist/writers/inheritance.d.ts.map +1 -0
  228. package/dist/writers/inheritance.js +90 -0
  229. package/dist/writers/inheritance.js.map +1 -0
  230. package/dist/writers/interface.d.ts +16 -0
  231. package/dist/writers/interface.d.ts.map +1 -0
  232. package/dist/writers/interface.js +155 -0
  233. package/dist/writers/interface.js.map +1 -0
  234. package/dist/writers/method.d.ts +150 -0
  235. package/dist/writers/method.d.ts.map +1 -0
  236. package/dist/writers/method.js +510 -0
  237. package/dist/writers/method.js.map +1 -0
  238. package/dist/writers/param-classify.d.ts +93 -0
  239. package/dist/writers/param-classify.d.ts.map +1 -0
  240. package/dist/writers/param-classify.js +163 -0
  241. package/dist/writers/param-classify.js.map +1 -0
  242. package/dist/writers/property-accessor.d.ts +19 -0
  243. package/dist/writers/property-accessor.d.ts.map +1 -0
  244. package/dist/writers/property-accessor.js +117 -0
  245. package/dist/writers/property-accessor.js.map +1 -0
  246. package/dist/writers/registration.d.ts +27 -0
  247. package/dist/writers/registration.d.ts.map +1 -0
  248. package/dist/writers/registration.js +30 -0
  249. package/dist/writers/registration.js.map +1 -0
  250. package/dist/writers/return-wrap.d.ts +39 -0
  251. package/dist/writers/return-wrap.d.ts.map +1 -0
  252. package/dist/writers/return-wrap.js +152 -0
  253. package/dist/writers/return-wrap.js.map +1 -0
  254. package/dist/writers/runtime-override.d.ts +18 -0
  255. package/dist/writers/runtime-override.d.ts.map +1 -0
  256. package/dist/writers/runtime-override.js +44 -0
  257. package/dist/writers/runtime-override.js.map +1 -0
  258. package/dist/writers/signal.d.ts +21 -0
  259. package/dist/writers/signal.d.ts.map +1 -0
  260. package/dist/writers/signal.js +260 -0
  261. package/dist/writers/signal.js.map +1 -0
  262. package/dist/writers/ts-type.d.ts +50 -0
  263. package/dist/writers/ts-type.d.ts.map +1 -0
  264. package/dist/writers/ts-type.js +75 -0
  265. package/dist/writers/ts-type.js.map +1 -0
  266. package/dist/writers/types-ts.d.ts +27 -0
  267. package/dist/writers/types-ts.d.ts.map +1 -0
  268. package/dist/writers/types-ts.js +97 -0
  269. package/dist/writers/types-ts.js.map +1 -0
  270. package/dist/writers/value.d.ts +89 -0
  271. package/dist/writers/value.d.ts.map +1 -0
  272. package/dist/writers/value.js +402 -0
  273. package/dist/writers/value.js.map +1 -0
  274. package/package.json +55 -0
  275. package/src/dsl/context.ts +146 -0
  276. package/src/dsl/emit.ts +42 -0
  277. package/src/dsl/identifier.ts +52 -0
  278. package/src/dsl/imports.ts +109 -0
  279. package/src/dsl/module.ts +75 -0
  280. package/src/ffi/pipeline.ts +113 -0
  281. package/src/fingerprint.ts +50 -0
  282. package/src/gi-store.ts +199 -0
  283. package/src/gir/boxed.ts +94 -0
  284. package/src/gir/callback.ts +26 -0
  285. package/src/gir/class.ts +79 -0
  286. package/src/gir/enum.ts +55 -0
  287. package/src/gir/field.ts +40 -0
  288. package/src/gir/function.ts +67 -0
  289. package/src/gir/namespace.ts +118 -0
  290. package/src/gir/parameter.ts +120 -0
  291. package/src/gir/parse.ts +171 -0
  292. package/src/gir/primitives.ts +137 -0
  293. package/src/gir/property.ts +39 -0
  294. package/src/gir/qualified-name.ts +55 -0
  295. package/src/gir/qualify.ts +64 -0
  296. package/src/gir/repository.ts +149 -0
  297. package/src/gir/signal.ts +36 -0
  298. package/src/gir/size.ts +129 -0
  299. package/src/gir/type-ref.ts +180 -0
  300. package/src/index.ts +16 -0
  301. package/src/jsx-store.ts +53 -0
  302. package/src/react/compounds-meta.ts +99 -0
  303. package/src/react/compounds.ts +204 -0
  304. package/src/react/internal.ts +91 -0
  305. package/src/react/jsx.ts +155 -0
  306. package/src/react/pipeline.ts +29 -0
  307. package/src/react/props.ts +307 -0
  308. package/src/react/slot-props.ts +57 -0
  309. package/src/react/widgets.ts +185 -0
  310. package/src/runner.ts +89 -0
  311. package/src/store-fs.ts +157 -0
  312. package/src/transpile.ts +80 -0
  313. package/src/typecheck-store.ts +136 -0
  314. package/src/writers/alias.ts +19 -0
  315. package/src/writers/async.ts +44 -0
  316. package/src/writers/boxed-constructor.ts +105 -0
  317. package/src/writers/boxed-field-accessor.ts +306 -0
  318. package/src/writers/boxed-layout.ts +166 -0
  319. package/src/writers/boxed.ts +77 -0
  320. package/src/writers/callables.ts +339 -0
  321. package/src/writers/callback.ts +40 -0
  322. package/src/writers/class-struct-record.ts +75 -0
  323. package/src/writers/class-struct.ts +96 -0
  324. package/src/writers/class.ts +384 -0
  325. package/src/writers/constant.ts +29 -0
  326. package/src/writers/constructor-props.ts +130 -0
  327. package/src/writers/enum.ts +53 -0
  328. package/src/writers/function.ts +136 -0
  329. package/src/writers/gtype-binding.ts +48 -0
  330. package/src/writers/inheritance.ts +104 -0
  331. package/src/writers/interface.ts +168 -0
  332. package/src/writers/method.ts +678 -0
  333. package/src/writers/param-classify.ts +178 -0
  334. package/src/writers/property-accessor.ts +139 -0
  335. package/src/writers/registration.ts +42 -0
  336. package/src/writers/return-wrap.ts +180 -0
  337. package/src/writers/runtime-override.ts +59 -0
  338. package/src/writers/signal.ts +318 -0
  339. package/src/writers/ts-type.ts +115 -0
  340. package/src/writers/value.ts +485 -0
@@ -0,0 +1,384 @@
1
+ import { toCamelCase, toPascalCase } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../dsl/context.js";
3
+ import { indent } from "../dsl/emit.js";
4
+ import type { GirClass } from "../gir/class.js";
5
+ import type { GirFunction } from "../gir/function.js";
6
+ import { splitQualifiedName } from "../gir/qualified-name.js";
7
+ import { qualifyFunction, qualifyTypeRef } from "../gir/qualify.js";
8
+ import type { GirTypeRef } from "../gir/type-ref.js";
9
+ import { matchAsyncFinishName } from "./async.js";
10
+ import {
11
+ appendMethodBinding,
12
+ appendShadowedAliases,
13
+ type Callables,
14
+ dedupeCallables,
15
+ emitBindings,
16
+ indexMethodsByName,
17
+ renderInstanceMethod,
18
+ renderStaticHead,
19
+ } from "./callables.js";
20
+ import { renderVfuncMetadata } from "./class-struct.js";
21
+ import { renderClassConstructor, renderConstructorPropsInterface } from "./constructor-props.js";
22
+ import { renderGetTypeReference } from "./gtype-binding.js";
23
+ import { collectInterfaceProperties, forEachAncestor, resolveImplementedInterface } from "./inheritance.js";
24
+ import { methodExportName, renderPromisifiedBody, renderPromisifiedSignature } from "./method.js";
25
+ import { inputParameters } from "./param-classify.js";
26
+ import { renderPropertyAccessor } from "./property-accessor.js";
27
+ import { appendNativeClassRegistration } from "./registration.js";
28
+ import { renderRuntimeOverride } from "./runtime-override.js";
29
+ import { renderSignalMembers } from "./signal.js";
30
+ import { renderTsType } from "./ts-type.js";
31
+
32
+ /**
33
+ * Emits a full class declaration for a `<class>` element.
34
+ *
35
+ * Walks the class's constructors, static functions, and methods, emitting
36
+ * each as a JS method body on the class declaration plus a hoisted `const`
37
+ * binding for the corresponding FFI symbol. Properties and signals are
38
+ * surfaced by their own writers; this function owns the class shape and
39
+ * the GType / class-struct registration calls.
40
+ *
41
+ * @param context - The module context
42
+ * @param klass - The class to emit
43
+ */
44
+ export const emitClass = (context: ModuleContext, klass: GirClass): void => {
45
+ if (!klass.introspectable) return;
46
+ if (klass.name.length === 0) return;
47
+ const className = toPascalCase(klass.name);
48
+ const callables: Callables = {
49
+ constructors: dedupeCallables(klass.constructors),
50
+ functions: dedupeCallables(klass.functions),
51
+ methods: dedupeCallables(klass.methods),
52
+ };
53
+ emitBindings(context, callables);
54
+
55
+ const parentExpression = resolveParent(context, klass);
56
+ const extendsClause = parentExpression === undefined ? "" : ` extends ${parentExpression}`;
57
+ const members = renderClassMembers(context, klass, callables, parentExpression !== undefined);
58
+ const body = members.map((member) => indent(member, 1)).join("\n\n");
59
+ context.module.appendDeclaration(`export class ${className}${extendsClause} {\n${body}\n}`);
60
+ context.module.appendDeclaration(renderConstructorPropsInterface(context, klass, className));
61
+
62
+ const interfaceRefs = klass.implements
63
+ .map((name) => resolveImplementsReference(context, name))
64
+ .filter((entry): entry is string => entry !== undefined);
65
+ if (interfaceRefs.length > 0) {
66
+ context.module.appendDeclaration(`export interface ${className} extends ${interfaceRefs.join(", ")} {}`);
67
+ }
68
+
69
+ appendClassRegistrations(context, klass, className);
70
+ };
71
+
72
+ const renderClassMembers = (
73
+ context: ModuleContext,
74
+ klass: GirClass,
75
+ callables: Callables,
76
+ hasParent: boolean,
77
+ ): readonly string[] => {
78
+ const className = toPascalCase(klass.name);
79
+ const members: string[] = ["declare __gtype__: number;"];
80
+ const constructorBlock = renderClassConstructor(context, klass, className, hasParent);
81
+ if (constructorBlock !== undefined) members.push(constructorBlock);
82
+ const claimedNames = new Set<string>();
83
+ members.push(...renderStaticHead(context, callables, className, "gobject"));
84
+ const inherited = collectInheritedMethods(context, klass);
85
+ const methodByName = indexMethodsByName(callables.methods);
86
+ appendInstanceMethods({
87
+ context,
88
+ methods: callables.methods,
89
+ methodByName,
90
+ inherited,
91
+ members,
92
+ claimedNames,
93
+ className,
94
+ });
95
+ appendShadowedAliases({ context, methods: callables.methods, methodByName, members, claimedNames });
96
+ appendFlattenedInterfaceMethods({ context, klass, inheritedNames: inherited.names, members, claimedNames });
97
+ for (const property of klass.properties) {
98
+ const block = renderPropertyAccessor(context, property, claimedNames, methodByName);
99
+ if (block !== undefined) members.push(block);
100
+ }
101
+ for (const property of collectInterfaceProperties(context, klass)) {
102
+ const block = renderPropertyAccessor(context, property, claimedNames, methodByName);
103
+ if (block !== undefined) members.push(block);
104
+ }
105
+ members.push(...renderSignalMembers(context, klass));
106
+ return members;
107
+ };
108
+
109
+ type AppendInstanceMethodsOptions = {
110
+ readonly context: ModuleContext;
111
+ readonly methods: readonly GirFunction[];
112
+ readonly methodByName: ReadonlyMap<string, GirFunction>;
113
+ readonly inherited: InheritedMethods;
114
+ readonly members: string[];
115
+ readonly claimedNames: Set<string>;
116
+ readonly className: string;
117
+ };
118
+
119
+ const appendInstanceMethods = (options: AppendInstanceMethodsOptions): void => {
120
+ const { context, methods, methodByName, inherited, members, claimedNames, className } = options;
121
+ for (const callable of methods) {
122
+ const rename = conflictRename(context, callable, inherited, className);
123
+ const block = renderClassInstanceMember(context, callable, methodByName, rename);
124
+ if (block === undefined) continue;
125
+ members.push(block);
126
+ claimedNames.add(rename ?? methodExportName(callable));
127
+ }
128
+ };
129
+
130
+ /**
131
+ * The disambiguated name a colliding instance method is emitted under:
132
+ * `<lowerClassName><MethodName>` (e.g. `iconViewSetCursor`).
133
+ */
134
+ const conflictingMethodName = (className: string, methodName: string): string => {
135
+ const camel = toCamelCase(className);
136
+ return `${camel.charAt(0).toLowerCase()}${camel.slice(1)}${toPascalCase(methodName)}`;
137
+ };
138
+
139
+ type AppendFlattenedInterfaceMethodsOptions = {
140
+ readonly context: ModuleContext;
141
+ readonly klass: GirClass;
142
+ readonly inheritedNames: ReadonlySet<string>;
143
+ readonly members: string[];
144
+ readonly claimedNames: Set<string>;
145
+ };
146
+
147
+ /**
148
+ * Copies every interface method onto the implementing class.
149
+ *
150
+ * GObject interfaces are structural at the C level: a class that implements
151
+ * `Gdk.Paintable` can be passed to `gdk_paintable_snapshot`, but the JS
152
+ * wrapper only exposes that method if codegen emits it. Each implemented
153
+ * interface's methods are re-rooted to the interface's namespace, bound
154
+ * against the implementing class's shared library (the loader resolves the
155
+ * symbol through that library's dependency graph), and emitted as direct
156
+ * instance members. Names already provided by the class itself or inherited
157
+ * from an ancestor (or an ancestor-implemented interface) are skipped so the
158
+ * most-derived definition wins and no member is declared twice.
159
+ *
160
+ * @param options - {@link AppendFlattenedInterfaceMethodsOptions}
161
+ */
162
+ const appendFlattenedInterfaceMethods = (options: AppendFlattenedInterfaceMethodsOptions): void => {
163
+ const { context, klass, inheritedNames, members, claimedNames } = options;
164
+ for (const implementName of klass.implements) {
165
+ const iface = resolveImplementedInterface(context, implementName);
166
+ if (iface === undefined) continue;
167
+ const methods = dedupeCallables(iface.klass.methods).map((method) =>
168
+ qualifyFunction(method, iface.namespaceName),
169
+ );
170
+ const methodByName = indexMethodsByName(methods);
171
+ for (const method of methods) {
172
+ const name = methodExportName(method);
173
+ if (name === "constructor") continue;
174
+ if (claimedNames.has(name) || inheritedNames.has(name)) continue;
175
+ const block = renderClassInstanceMember(context, method, methodByName);
176
+ if (block === undefined) continue;
177
+ appendMethodBinding(context, method);
178
+ members.push(block);
179
+ claimedNames.add(name);
180
+ }
181
+ }
182
+ };
183
+
184
+ const appendClassRegistrations = (context: ModuleContext, klass: GirClass, className: string): void => {
185
+ const getTypeRef =
186
+ klass.glibGetType === undefined
187
+ ? undefined
188
+ : renderGetTypeReference(context, klass.glibGetType, klass.glibTypeName);
189
+ appendNativeClassRegistration(context, {
190
+ className,
191
+ role: "class",
192
+ getTypeRef,
193
+ vfuncs: renderVfuncMetadata(context, klass),
194
+ });
195
+ };
196
+
197
+ const renderClassInstanceMember = (
198
+ context: ModuleContext,
199
+ callable: GirFunction,
200
+ siblings: ReadonlyMap<string, GirFunction>,
201
+ nameOverride?: string,
202
+ ): string | undefined => {
203
+ if (!callable.introspectable || callable.shadowedBy !== undefined || callable.cIdentifier === undefined) {
204
+ return undefined;
205
+ }
206
+ const name = nameOverride ?? methodExportName(callable);
207
+ if (name === "constructor") return undefined;
208
+ const override = renderRuntimeOverride(callable, name);
209
+ if (override !== undefined) return override;
210
+ const promisified = renderPromisifiedMember(context, callable, siblings, name);
211
+ if (promisified !== undefined) return promisified;
212
+ return renderInstanceMethod(context, callable, nameOverride);
213
+ };
214
+
215
+ const renderPromisifiedMember = (
216
+ context: ModuleContext,
217
+ callable: GirFunction,
218
+ siblings: ReadonlyMap<string, GirFunction>,
219
+ name: string,
220
+ ): string | undefined => {
221
+ const finishName = matchAsyncFinishName(callable, [...siblings.values()]);
222
+ if (finishName === undefined) return undefined;
223
+ const finishFn = siblings.get(finishName);
224
+ if (finishFn === undefined) return undefined;
225
+ const cIdentifier = callable.cIdentifier;
226
+ if (cIdentifier === undefined) return undefined;
227
+ const { signature, returnType } = renderPromisifiedSignature(context, callable, finishFn);
228
+ const finishMember = methodExportName(finishFn);
229
+ const body = renderPromisifiedBody(context, callable, finishMember, cIdentifier);
230
+ return `${name}(${signature}): ${returnType} {\n${indent(body, 1)}\n}`;
231
+ };
232
+
233
+ const resolveImplementsReference = (context: ModuleContext, name: string): string | undefined => {
234
+ const { namespaceName, typeName } = splitQualifiedName(name, context.namespace.name);
235
+ const resolved = context.repository.resolveNamed(namespaceName, typeName);
236
+ if (resolved === undefined || resolved.kind !== "interface") return undefined;
237
+ if (namespaceName === context.namespace.name) return toPascalCase(typeName);
238
+ const alias = context.addCrossNamespaceImport(namespaceName);
239
+ return `${alias}.${toPascalCase(typeName)}`;
240
+ };
241
+
242
+ /** An ancestor method together with the namespace its type references resolve against. */
243
+ type InheritedMethod = {
244
+ readonly method: GirFunction;
245
+ readonly namespaceName: string;
246
+ };
247
+
248
+ type InheritedMethods = {
249
+ /** camelCase method name → its TypeScript return type, for ancestor class methods. */
250
+ readonly returnTypes: ReadonlyMap<string, string>;
251
+ /** camelCase method name → the nearest ancestor definition it overrides. */
252
+ readonly definitions: ReadonlyMap<string, InheritedMethod>;
253
+ /** camelCase names of every method reachable through ancestors and the interfaces they implement. */
254
+ readonly names: ReadonlySet<string>;
255
+ };
256
+
257
+ /** Mutable accumulator threaded through ancestor traversal. */
258
+ type InheritedMethodsAccumulator = {
259
+ readonly returnTypes: Map<string, string>;
260
+ readonly definitions: Map<string, InheritedMethod>;
261
+ readonly names: Set<string>;
262
+ };
263
+
264
+ const collectInheritedMethods = (context: ModuleContext, klass: GirClass): InheritedMethods => {
265
+ const accumulator: InheritedMethodsAccumulator = {
266
+ returnTypes: new Map<string, string>(),
267
+ definitions: new Map<string, InheritedMethod>(),
268
+ names: new Set<string>(),
269
+ };
270
+ forEachAncestor(context, klass, (ancestor) => {
271
+ absorbInheritedMethods(context, ancestor, accumulator);
272
+ absorbInheritedInterfaceMethodNames(context, ancestor, accumulator.names);
273
+ });
274
+ return accumulator;
275
+ };
276
+
277
+ const absorbInheritedMethods = (
278
+ context: ModuleContext,
279
+ resolved: { readonly klass: GirClass; readonly namespaceName: string },
280
+ accumulator: InheritedMethodsAccumulator,
281
+ ): void => {
282
+ const { returnTypes, definitions, names } = accumulator;
283
+ for (const method of resolved.klass.methods) {
284
+ if (!method.introspectable) continue;
285
+ const name = toCamelCase(method.name);
286
+ names.add(name);
287
+ if (returnTypes.has(name)) continue;
288
+ definitions.set(name, { method, namespaceName: resolved.namespaceName });
289
+ const qualifiedType = qualifyTypeRef(method.returnValue.type, resolved.namespaceName);
290
+ returnTypes.set(name, renderTsType(context, qualifiedType, method.returnValue.nullable));
291
+ }
292
+ };
293
+
294
+ const absorbInheritedInterfaceMethodNames = (
295
+ context: ModuleContext,
296
+ ancestor: { readonly klass: GirClass; readonly namespaceName: string },
297
+ names: Set<string>,
298
+ ): void => {
299
+ for (const implementName of ancestor.klass.implements) {
300
+ const iface = resolveImplementedInterface(context, implementName, ancestor.namespaceName);
301
+ if (iface === undefined) continue;
302
+ for (const method of iface.klass.methods) {
303
+ if (!method.introspectable) continue;
304
+ names.add(toCamelCase(method.name));
305
+ }
306
+ }
307
+ };
308
+
309
+ /**
310
+ * The disambiguated name an instance method is emitted under when it collides
311
+ * incompatibly with an inherited method, or `undefined` when no rename applies.
312
+ *
313
+ * A collision is incompatible when the inherited method of the same name has a
314
+ * different return type, a distinct enum at a parameter position, or a
315
+ * different input-parameter arity — each of which would make the override
316
+ * structurally unassignable to its base. The colliding method is renamed so
317
+ * both it and the inherited method stay callable.
318
+ */
319
+ const conflictRename = (
320
+ context: ModuleContext,
321
+ callable: GirFunction,
322
+ inherited: InheritedMethods,
323
+ className: string,
324
+ ): string | undefined => {
325
+ if (!callable.introspectable) return undefined;
326
+ const name = methodExportName(callable);
327
+ const inheritedReturn = inherited.returnTypes.get(name);
328
+ const inheritedMethod = inherited.definitions.get(name);
329
+ if (inheritedReturn === undefined || inheritedMethod === undefined) return undefined;
330
+ const ownReturn = renderTsType(context, callable.returnValue.type, callable.returnValue.nullable);
331
+ const conflicts =
332
+ inheritedReturn !== ownReturn ||
333
+ hasParameterEnumConflict(context, callable, inheritedMethod) ||
334
+ inputParameters(callable).length !== inputParameters(inheritedMethod.method).length;
335
+ return conflicts ? conflictingMethodName(className, callable.name) : undefined;
336
+ };
337
+
338
+ /**
339
+ * Whether an override pairs a distinct enum against the inherited method at
340
+ * any input-parameter position.
341
+ *
342
+ * Numeric enums are mutually assignable with `number`, so a `number`/enum
343
+ * pairing is compatible; two *different* enums are not, which would make the
344
+ * derived class structurally unassignable to its base. Such an override is
345
+ * dropped so the inherited signature stands.
346
+ *
347
+ * @param context - The module context
348
+ * @param own - The override declared on the derived class
349
+ * @param inherited - The nearest ancestor definition of the same name
350
+ */
351
+ const hasParameterEnumConflict = (context: ModuleContext, own: GirFunction, inherited: InheritedMethod): boolean => {
352
+ const ownParams = inputParameters(own);
353
+ const inheritedParams = inputParameters(inherited.method);
354
+ const count = Math.min(ownParams.length, inheritedParams.length);
355
+ for (let index = 0; index < count; index += 1) {
356
+ const ownParam = ownParams[index];
357
+ const inheritedParam = inheritedParams[index];
358
+ if (ownParam === undefined || inheritedParam === undefined) continue;
359
+ const ownEnum = enumIdentity(context, ownParam.parameter.type, context.namespace.name);
360
+ const inheritedEnum = enumIdentity(context, inheritedParam.parameter.type, inherited.namespaceName);
361
+ if (ownEnum !== undefined && inheritedEnum !== undefined && ownEnum !== inheritedEnum) return true;
362
+ }
363
+ return false;
364
+ };
365
+
366
+ const enumIdentity = (
367
+ context: ModuleContext,
368
+ ref: GirTypeRef | undefined,
369
+ defaultNamespace: string,
370
+ ): string | undefined => {
371
+ const qualified = qualifyTypeRef(ref, defaultNamespace);
372
+ if (qualified === undefined || qualified.kind !== "named") return undefined;
373
+ const resolved = context.repository.resolveNamed(qualified.namespaceName ?? defaultNamespace, qualified.typeName);
374
+ if (resolved === undefined || resolved.kind !== "enum") return undefined;
375
+ return `${resolved.namespace.name}.${resolved.value.name}`;
376
+ };
377
+
378
+ const resolveParent = (context: ModuleContext, klass: GirClass): string | undefined => {
379
+ if (klass.parent === undefined) return undefined;
380
+ const { namespaceName, typeName } = splitQualifiedName(klass.parent, context.namespace.name);
381
+ if (namespaceName === context.namespace.name) return toPascalCase(typeName);
382
+ const alias = context.addCrossNamespaceImport(namespaceName);
383
+ return `${alias}.${toPascalCase(typeName)}`;
384
+ };
@@ -0,0 +1,29 @@
1
+ import { quote } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../dsl/context.js";
3
+ import type { GirConstant } from "../gir/namespace.js";
4
+ import type { GirTypeRef } from "../gir/type-ref.js";
5
+
6
+ /**
7
+ * Emits an `export const NAME = value` declaration for a namespace constant.
8
+ *
9
+ * A constant whose declared type marshals to a string (`utf8` / `filename` /
10
+ * `gchararray`) is always quoted — even when its value is all digits, as with
11
+ * `G_CSET_DIGITS = "0123456789"`. Other constants keep the bare-numeral form
12
+ * when the value is a plain number and are quoted otherwise.
13
+ *
14
+ * @param context - The module context
15
+ * @param constant - The constant
16
+ */
17
+ export const emitConstant = (context: ModuleContext, constant: GirConstant): void => {
18
+ context.module.appendDeclaration(`export const ${constant.name} = ${constantLiteral(constant)};`);
19
+ };
20
+
21
+ const constantLiteral = (constant: GirConstant): string => {
22
+ if (isStringTyped(constant.type)) return quote(constant.value);
23
+ return isNumericLiteral(constant.value) ? constant.value : quote(constant.value);
24
+ };
25
+
26
+ const isStringTyped = (type: GirTypeRef | undefined): boolean =>
27
+ type?.kind === "primitive" && type.category === "string";
28
+
29
+ const isNumericLiteral = (value: string): boolean => /^-?(?:\d+|\d*\.\d+)$/.test(value);
@@ -0,0 +1,130 @@
1
+ import { quote, toCamelCase, toIdentifier, toPascalCase } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../dsl/context.js";
3
+ import { indent } from "../dsl/emit.js";
4
+ import type { GirClass } from "../gir/class.js";
5
+ import type { GirProperty } from "../gir/property.js";
6
+ import { splitQualifiedName } from "../gir/qualified-name.js";
7
+ import { collectInterfaceProperties } from "./inheritance.js";
8
+ import { renderTsType } from "./ts-type.js";
9
+ import { renderFfiType } from "./value.js";
10
+
11
+ /**
12
+ * Whether a property can be passed to a constructor: writable, construct, or
13
+ * construct-only properties only. Read-only properties cannot be set through
14
+ * `g_object_new_with_properties`.
15
+ *
16
+ * @param property - The GIR property
17
+ */
18
+ const isConstructable = (property: GirProperty): boolean =>
19
+ property.writable || property.construct || property.constructOnly;
20
+
21
+ /**
22
+ * The constructable properties a class introduces: its own writable/construct
23
+ * properties plus those it inherits from directly-implemented interfaces,
24
+ * deduplicated by camelCase JS name (the class's own declaration wins).
25
+ *
26
+ * Ancestor class properties are intentionally excluded — each class translates
27
+ * only the props it introduces and forwards the rest up the `super(...)` chain.
28
+ *
29
+ * @param context - The module context
30
+ * @param klass - The class whose construct props to collect
31
+ */
32
+ const collectConstructableProps = (context: ModuleContext, klass: GirClass): readonly GirProperty[] => {
33
+ const all = [...klass.properties, ...collectInterfaceProperties(context, klass)].filter(isConstructable);
34
+ const seen = new Set<string>();
35
+ const result: GirProperty[] = [];
36
+ for (const property of all) {
37
+ const jsName = toIdentifier(toCamelCase(property.name));
38
+ if (seen.has(jsName)) continue;
39
+ seen.add(jsName);
40
+ result.push(property);
41
+ }
42
+ return result;
43
+ };
44
+
45
+ /**
46
+ * Renders the `export interface <Class>ConstructorProps` declaration.
47
+ *
48
+ * Each interface lists the camelCase, optional, nullable form of every
49
+ * property the class introduces and `extends` its parent's interface, so the
50
+ * full inherited prop set is available when typing `new <Class>(props)`.
51
+ *
52
+ * @param context - The module context
53
+ * @param klass - The class to render props for
54
+ * @param className - The local PascalCase class name
55
+ */
56
+ export const renderConstructorPropsInterface = (context: ModuleContext, klass: GirClass, className: string): string => {
57
+ const parentRef = resolveParentPropsReference(context, klass);
58
+ const extendsClause = parentRef === undefined ? "" : ` extends ${parentRef}`;
59
+ const lines = collectConstructableProps(context, klass).map(
60
+ (property) => `${toIdentifier(toCamelCase(property.name))}?: ${renderTsType(context, property.type, true)};`,
61
+ );
62
+ const body = lines.length === 0 ? "" : `\n${indent(lines.join("\n"), 1)}\n`;
63
+ return `export interface ${className}ConstructorProps${extendsClause} {${body}}`;
64
+ };
65
+
66
+ /**
67
+ * Renders the constructor for a class, or `undefined` when none is needed.
68
+ *
69
+ * The parentless root of a GObject hierarchy gets the canonical base
70
+ * constructor that hands the assembled `GValue` record to
71
+ * `constructGObjectInstance`. A class that introduces constructable props gets
72
+ * a constructor that destructures and translates those props into `GValue`s,
73
+ * spreads the untranslated remainder, and forwards everything to `super`. A
74
+ * class that introduces no props gets no constructor and inherits its nearest
75
+ * typed ancestor's, keeping the chain free of redundant hops.
76
+ *
77
+ * @param context - The module context
78
+ * @param klass - The class being emitted
79
+ * @param className - The local PascalCase class name
80
+ * @param hasParent - Whether the class declares an `extends` clause
81
+ */
82
+ export const renderClassConstructor = (
83
+ context: ModuleContext,
84
+ klass: GirClass,
85
+ className: string,
86
+ hasParent: boolean,
87
+ ): string | undefined => {
88
+ if (!hasParent) return renderRootConstructor(context);
89
+ const props = collectConstructableProps(context, klass);
90
+ if (props.length === 0) return undefined;
91
+ return renderTranslatingConstructor(context, props, className);
92
+ };
93
+
94
+ /** The forwarding record of marshalled `GValue`s a constructor hands to `super`. */
95
+ const GVALUE_RECORD = "Record<string, GValue | undefined>";
96
+
97
+ const renderRootConstructor = (context: ModuleContext): string => {
98
+ context.addConstructGObjectInstanceImport();
99
+ context.addRuntimeImport("GValue");
100
+ const body = "constructGObjectInstance(this, props);";
101
+ return `constructor(props: ${GVALUE_RECORD} = {}) {\n${indent(body, 1)}\n}`;
102
+ };
103
+
104
+ const renderTranslatingConstructor = (
105
+ context: ModuleContext,
106
+ props: readonly GirProperty[],
107
+ className: string,
108
+ ): string => {
109
+ context.addValueFromFfiOptionalImport();
110
+ context.addRuntimeImport("GValue");
111
+ const destructured = props.map((property) => toIdentifier(toCamelCase(property.name)));
112
+ const pattern = `{ ${[...destructured, "...rest"].join(", ")} }`;
113
+ const entries = props.map(
114
+ (property) =>
115
+ `${quote(property.name)}: valueFromFfiOptional(${renderFfiType(context, property.type, property.transferOwnership)}, ${toIdentifier(toCamelCase(property.name))}),`,
116
+ );
117
+ const recordLiteral = `{\n${indent(entries.join("\n"), 1)}\n}`;
118
+ const lines = [`const props: ${GVALUE_RECORD} = ${recordLiteral};`, "super({ ...props, ...rest });"];
119
+ const body = lines.join("\n");
120
+ return `constructor(${pattern}: ${className}ConstructorProps = {}) {\n${indent(body, 1)}\n}`;
121
+ };
122
+
123
+ const resolveParentPropsReference = (context: ModuleContext, klass: GirClass): string | undefined => {
124
+ if (klass.parent === undefined) return undefined;
125
+ const { namespaceName, typeName } = splitQualifiedName(klass.parent, context.namespace.name);
126
+ const propsName = `${toPascalCase(typeName)}ConstructorProps`;
127
+ if (namespaceName === context.namespace.name) return propsName;
128
+ const alias = context.addCrossNamespaceImport(namespaceName);
129
+ return `${alias}.${propsName}`;
130
+ };
@@ -0,0 +1,53 @@
1
+ import { quote } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../dsl/context.js";
3
+ import type { GirEnum } from "../gir/enum.js";
4
+
5
+ /**
6
+ * Emits the runtime declaration for an `<enumeration>` or `<bitfield>`.
7
+ *
8
+ * Two branches:
9
+ * - `glib:error-domain` present → `export type Name = number` plus
10
+ * `createErrorDomain(quark, members)` typed as `ErrorDomain<{ members }>`,
11
+ * since the domain object must stay `instanceof`-matchable.
12
+ * - GLib-registered enum/flags or C-only enum (no GLib registration) →
13
+ * a TypeScript `enum Name { members }` declaration.
14
+ *
15
+ * Member names are derived from the GIR `name` attribute, uppercased with
16
+ * hyphens normalized to underscores. Names that would start with a digit
17
+ * (e.g. `5_6_5` → `_5_6_5`) are prefixed with an underscore.
18
+ *
19
+ * @param context - The module context
20
+ * @param enumeration - The enum to emit
21
+ */
22
+ export const emitEnum = (context: ModuleContext, enumeration: GirEnum): void => {
23
+ if (!enumeration.introspectable) return;
24
+ const name = enumeration.name;
25
+ const memberKeys = enumeration.members.map((member) => memberKey(member.name));
26
+ if (enumeration.errorDomain !== undefined) {
27
+ const memberEntries = enumeration.members.map((member, index) => `${memberKeys[index]}: ${member.value}`);
28
+ const typeFields = memberKeys.map((key) => `readonly ${key}: number`).join("; ");
29
+ context.addRuntimeImport("createErrorDomain");
30
+ context.addRuntimeImport("ErrorDomain");
31
+ const quarkExpression = renderQuarkExpression(context, enumeration.errorDomain);
32
+ context.module.appendDeclaration(`export type ${name} = number;`);
33
+ context.module.appendDeclaration(
34
+ `export const ${name}: ErrorDomain<{ ${typeFields} }> = createErrorDomain(${quarkExpression}, { ${memberEntries.join(", ")} });`,
35
+ );
36
+ return;
37
+ }
38
+ const memberDeclarations = enumeration.members.map((member, index) => `${memberKeys[index]} = ${member.value}`);
39
+ context.module.appendDeclaration(`export enum ${name} { ${memberDeclarations.join(", ")} }`);
40
+ };
41
+
42
+ const memberKey = (name: string): string => {
43
+ const upper = name.toUpperCase().replaceAll("-", "_");
44
+ return /^[0-9]/.test(upper) ? `_${upper}` : upper;
45
+ };
46
+
47
+ const renderQuarkExpression = (context: ModuleContext, errorDomain: string): string => {
48
+ if (context.namespace.name === "GLib") {
49
+ return `() => quarkFromString(${quote(errorDomain)})`;
50
+ }
51
+ const alias = context.addCrossNamespaceImport("GLib");
52
+ return `() => ${alias}.quarkFromString(${quote(errorDomain)})`;
53
+ };