@gtkx/codegen 1.0.0-rc.2 → 1.0.0-rc.4

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 (254) hide show
  1. package/README.md +4 -5
  2. package/dist/analysis/descriptor-render.d.ts +2 -2
  3. package/dist/analysis/descriptor-render.d.ts.map +1 -1
  4. package/dist/analysis/descriptor-render.js +30 -32
  5. package/dist/analysis/descriptor-render.js.map +1 -1
  6. package/dist/analysis/descriptor.d.ts +6 -6
  7. package/dist/analysis/descriptor.d.ts.map +1 -1
  8. package/dist/analysis/descriptor.js +7 -7
  9. package/dist/analysis/descriptor.js.map +1 -1
  10. package/dist/analysis/param-structure.d.ts +2 -2
  11. package/dist/analysis/param-structure.d.ts.map +1 -1
  12. package/dist/analysis/param-structure.js +5 -7
  13. package/dist/analysis/param-structure.js.map +1 -1
  14. package/dist/analysis/ts-type.d.ts +1 -1
  15. package/dist/analysis/ts-type.d.ts.map +1 -1
  16. package/dist/analysis/ts-type.js +7 -13
  17. package/dist/analysis/ts-type.js.map +1 -1
  18. package/dist/compile.d.ts.map +1 -1
  19. package/dist/compile.js +2 -1
  20. package/dist/compile.js.map +1 -1
  21. package/dist/docs/api-reference.d.ts +77 -1
  22. package/dist/docs/api-reference.d.ts.map +1 -1
  23. package/dist/docs/api-reference.js +66 -8
  24. package/dist/docs/api-reference.js.map +1 -1
  25. package/dist/docs/element-page.d.ts.map +1 -1
  26. package/dist/docs/element-page.js +5 -4
  27. package/dist/docs/element-page.js.map +1 -1
  28. package/dist/docs/pipeline.d.ts +5 -3
  29. package/dist/docs/pipeline.d.ts.map +1 -1
  30. package/dist/docs/pipeline.js +5 -3
  31. package/dist/docs/pipeline.js.map +1 -1
  32. package/dist/docs/render.js +6 -6
  33. package/dist/docs/render.js.map +1 -1
  34. package/dist/docs/symbol-page.d.ts +5 -0
  35. package/dist/docs/symbol-page.d.ts.map +1 -1
  36. package/dist/docs/symbol-page.js +2 -2
  37. package/dist/docs/symbol-page.js.map +1 -1
  38. package/dist/fingerprint.d.ts +2 -4
  39. package/dist/fingerprint.d.ts.map +1 -1
  40. package/dist/fingerprint.js +7 -6
  41. package/dist/fingerprint.js.map +1 -1
  42. package/dist/gi.d.ts +7 -10
  43. package/dist/gi.d.ts.map +1 -1
  44. package/dist/gi.js +4 -9
  45. package/dist/gi.js.map +1 -1
  46. package/dist/gir/field.d.ts +0 -1
  47. package/dist/gir/field.d.ts.map +1 -1
  48. package/dist/gir/field.js +0 -3
  49. package/dist/gir/field.js.map +1 -1
  50. package/dist/gir/gir-path.d.ts +7 -0
  51. package/dist/gir/gir-path.d.ts.map +1 -1
  52. package/dist/gir/gir-path.js +7 -0
  53. package/dist/gir/gir-path.js.map +1 -1
  54. package/dist/gir/libraries.d.ts +17 -3
  55. package/dist/gir/libraries.d.ts.map +1 -1
  56. package/dist/gir/libraries.js +14 -1
  57. package/dist/gir/libraries.js.map +1 -1
  58. package/dist/gir/parameter.d.ts.map +1 -1
  59. package/dist/gir/parameter.js +0 -4
  60. package/dist/gir/parameter.js.map +1 -1
  61. package/dist/gir/size.d.ts.map +1 -1
  62. package/dist/gir/size.js +0 -3
  63. package/dist/gir/size.js.map +1 -1
  64. package/dist/gir/type-id.d.ts +1 -1
  65. package/dist/gir/type-id.d.ts.map +1 -1
  66. package/dist/gir/type-id.js +1 -3
  67. package/dist/gir/type-id.js.map +1 -1
  68. package/dist/gir/type-ref.js +1 -1
  69. package/dist/gir/type-ref.js.map +1 -1
  70. package/dist/index.d.ts +18 -5
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +12 -2
  73. package/dist/index.js.map +1 -1
  74. package/dist/internal.d.ts +3 -0
  75. package/dist/internal.d.ts.map +1 -0
  76. package/dist/internal.js +3 -0
  77. package/dist/internal.js.map +1 -0
  78. package/dist/jsx.d.ts +8 -11
  79. package/dist/jsx.d.ts.map +1 -1
  80. package/dist/jsx.js +18 -15
  81. package/dist/jsx.js.map +1 -1
  82. package/dist/khronos/args.d.ts.map +1 -1
  83. package/dist/khronos/args.js +1 -1
  84. package/dist/khronos/args.js.map +1 -1
  85. package/dist/khronos/pipeline.js +1 -1
  86. package/dist/khronos/pipeline.js.map +1 -1
  87. package/dist/khronos/plan.d.ts +2 -2
  88. package/dist/khronos/plan.d.ts.map +1 -1
  89. package/dist/khronos/plan.js +6 -6
  90. package/dist/khronos/plan.js.map +1 -1
  91. package/dist/react/element-config.d.ts +18 -9
  92. package/dist/react/element-config.d.ts.map +1 -1
  93. package/dist/react/element-config.js +23 -13
  94. package/dist/react/element-config.js.map +1 -1
  95. package/dist/runner.d.ts +36 -16
  96. package/dist/runner.d.ts.map +1 -1
  97. package/dist/runner.js +22 -20
  98. package/dist/runner.js.map +1 -1
  99. package/dist/store/compile-store.d.ts +1 -2
  100. package/dist/store/compile-store.d.ts.map +1 -1
  101. package/dist/store/compile-store.js +2 -2
  102. package/dist/store/compile-store.js.map +1 -1
  103. package/dist/store/gi/callables.d.ts.map +1 -1
  104. package/dist/store/gi/callables.js +4 -9
  105. package/dist/store/gi/callables.js.map +1 -1
  106. package/dist/store/gi/constant.d.ts.map +1 -1
  107. package/dist/store/gi/constant.js +0 -2
  108. package/dist/store/gi/constant.js.map +1 -1
  109. package/dist/store/gi/function.d.ts.map +1 -1
  110. package/dist/store/gi/function.js +1 -7
  111. package/dist/store/gi/function.js.map +1 -1
  112. package/dist/store/gi/method.d.ts.map +1 -1
  113. package/dist/store/gi/method.js +26 -23
  114. package/dist/store/gi/method.js.map +1 -1
  115. package/dist/store/gi/property-accessor.d.ts +1 -1
  116. package/dist/store/gi/property-accessor.d.ts.map +1 -1
  117. package/dist/store/gi/property-accessor.js +5 -7
  118. package/dist/store/gi/property-accessor.js.map +1 -1
  119. package/dist/store/gi/record-constructor.d.ts.map +1 -1
  120. package/dist/store/gi/record-constructor.js +5 -4
  121. package/dist/store/gi/record-constructor.js.map +1 -1
  122. package/dist/store/gi/record-field-accessor.d.ts +3 -2
  123. package/dist/store/gi/record-field-accessor.d.ts.map +1 -1
  124. package/dist/store/gi/record-field-accessor.js +21 -11
  125. package/dist/store/gi/record-field-accessor.js.map +1 -1
  126. package/dist/store/gi/record-layout.d.ts.map +1 -1
  127. package/dist/store/gi/record-layout.js +0 -4
  128. package/dist/store/gi/record-layout.js.map +1 -1
  129. package/dist/store/gi/record.d.ts.map +1 -1
  130. package/dist/store/gi/record.js +0 -7
  131. package/dist/store/gi/record.js.map +1 -1
  132. package/dist/store/gi/return-wrap.d.ts +1 -1
  133. package/dist/store/gi/return-wrap.d.ts.map +1 -1
  134. package/dist/store/gi/return-wrap.js +4 -4
  135. package/dist/store/gi/return-wrap.js.map +1 -1
  136. package/dist/store/gi/signal.d.ts.map +1 -1
  137. package/dist/store/gi/signal.js +13 -16
  138. package/dist/store/gi/signal.js.map +1 -1
  139. package/dist/store/gi/value-marshalable.d.ts +7 -0
  140. package/dist/store/gi/value-marshalable.d.ts.map +1 -0
  141. package/dist/store/gi/value-marshalable.js +57 -0
  142. package/dist/store/gi/value-marshalable.js.map +1 -0
  143. package/dist/store/gi-store.d.ts +2 -3
  144. package/dist/store/gi-store.d.ts.map +1 -1
  145. package/dist/store/gi-store.js.map +1 -1
  146. package/dist/store/jsx/element-components.d.ts +0 -2
  147. package/dist/store/jsx/element-components.d.ts.map +1 -1
  148. package/dist/store/jsx/element-components.js +0 -4
  149. package/dist/store/jsx/element-components.js.map +1 -1
  150. package/dist/store/jsx/element-prop-imports.d.ts +2 -3
  151. package/dist/store/jsx/element-prop-imports.d.ts.map +1 -1
  152. package/dist/store/jsx/element-prop-imports.js +0 -2
  153. package/dist/store/jsx/element-prop-imports.js.map +1 -1
  154. package/dist/store/jsx/element-prop-types.d.ts +0 -1
  155. package/dist/store/jsx/element-prop-types.d.ts.map +1 -1
  156. package/dist/store/jsx/element-prop-types.js +0 -1
  157. package/dist/store/jsx/element-prop-types.js.map +1 -1
  158. package/dist/store/jsx/generated-elements.d.ts +29 -0
  159. package/dist/store/jsx/generated-elements.d.ts.map +1 -0
  160. package/dist/store/jsx/generated-elements.js +30 -0
  161. package/dist/store/jsx/generated-elements.js.map +1 -0
  162. package/dist/store/jsx/intrinsic-elements.d.ts +1 -2
  163. package/dist/store/jsx/intrinsic-elements.d.ts.map +1 -1
  164. package/dist/store/jsx/intrinsic-elements.js +1 -2
  165. package/dist/store/jsx/intrinsic-elements.js.map +1 -1
  166. package/dist/store/jsx/jsx.js +6 -6
  167. package/dist/store/jsx/jsx.js.map +1 -1
  168. package/dist/store/jsx/metadata.js +7 -7
  169. package/dist/store/jsx/metadata.js.map +1 -1
  170. package/dist/store/jsx/omitted-props.d.ts +8 -0
  171. package/dist/store/jsx/omitted-props.d.ts.map +1 -0
  172. package/dist/store/jsx/omitted-props.js +20 -0
  173. package/dist/store/jsx/omitted-props.js.map +1 -0
  174. package/dist/store/jsx/pipeline.d.ts +4 -0
  175. package/dist/store/jsx/pipeline.d.ts.map +1 -1
  176. package/dist/store/jsx/pipeline.js +4 -1
  177. package/dist/store/jsx/pipeline.js.map +1 -1
  178. package/dist/store/jsx/props.d.ts +1 -1
  179. package/dist/store/jsx/props.d.ts.map +1 -1
  180. package/dist/store/jsx/props.js +8 -13
  181. package/dist/store/jsx/props.js.map +1 -1
  182. package/dist/store/jsx-store.d.ts +2 -3
  183. package/dist/store/jsx-store.d.ts.map +1 -1
  184. package/dist/store/jsx-store.js +3 -3
  185. package/dist/store/jsx-store.js.map +1 -1
  186. package/dist/store/resolve-store.d.ts +28 -0
  187. package/dist/store/resolve-store.d.ts.map +1 -0
  188. package/dist/store/resolve-store.js +65 -0
  189. package/dist/store/resolve-store.js.map +1 -0
  190. package/dist/store/store-fs.d.ts +5 -1
  191. package/dist/store/store-fs.d.ts.map +1 -1
  192. package/dist/store/store-fs.js +6 -4
  193. package/dist/store/store-fs.js.map +1 -1
  194. package/overrides/gobject/object.ts.ejs +8 -8
  195. package/package.json +6 -11
  196. package/src/analysis/descriptor-render.ts +45 -38
  197. package/src/analysis/descriptor.ts +13 -13
  198. package/src/analysis/param-structure.ts +8 -10
  199. package/src/analysis/ts-type.ts +8 -17
  200. package/src/compile.ts +2 -1
  201. package/src/docs/api-reference.ts +131 -13
  202. package/src/docs/element-page.ts +5 -9
  203. package/src/docs/pipeline.ts +9 -6
  204. package/src/docs/render.ts +7 -7
  205. package/src/docs/symbol-page.ts +7 -2
  206. package/src/fingerprint.ts +21 -12
  207. package/src/gi.ts +13 -11
  208. package/src/gir/field.ts +0 -4
  209. package/src/gir/gir-path.ts +7 -0
  210. package/src/gir/libraries.ts +18 -4
  211. package/src/gir/parameter.ts +0 -4
  212. package/src/gir/size.ts +0 -3
  213. package/src/gir/type-id.ts +2 -4
  214. package/src/gir/type-ref.ts +1 -1
  215. package/src/index.ts +18 -15
  216. package/src/internal.ts +2 -0
  217. package/src/jsx.ts +26 -21
  218. package/src/khronos/args.ts +1 -1
  219. package/src/khronos/pipeline.ts +1 -1
  220. package/src/khronos/plan.ts +8 -8
  221. package/src/react/element-config.ts +25 -16
  222. package/src/runner.ts +62 -37
  223. package/src/store/compile-store.ts +3 -4
  224. package/src/store/gi/callables.ts +5 -11
  225. package/src/store/gi/constant.ts +0 -2
  226. package/src/store/gi/function.ts +1 -7
  227. package/src/store/gi/method.ts +31 -28
  228. package/src/store/gi/property-accessor.ts +6 -8
  229. package/src/store/gi/record-constructor.ts +9 -4
  230. package/src/store/gi/record-field-accessor.ts +41 -12
  231. package/src/store/gi/record-layout.ts +0 -4
  232. package/src/store/gi/record.ts +0 -9
  233. package/src/store/gi/return-wrap.ts +5 -5
  234. package/src/store/gi/signal.ts +17 -17
  235. package/src/store/gi/value-marshalable.ts +87 -0
  236. package/src/store/gi-store.ts +2 -4
  237. package/src/store/jsx/element-components.ts +0 -6
  238. package/src/store/jsx/element-prop-imports.ts +7 -4
  239. package/src/store/jsx/element-prop-types.ts +0 -1
  240. package/src/store/jsx/generated-elements.ts +60 -0
  241. package/src/store/jsx/intrinsic-elements.ts +0 -9
  242. package/src/store/jsx/jsx.ts +7 -7
  243. package/src/store/jsx/metadata.ts +8 -8
  244. package/src/store/jsx/omitted-props.ts +30 -0
  245. package/src/store/jsx/pipeline.ts +6 -1
  246. package/src/store/jsx/props.ts +8 -20
  247. package/src/store/jsx-store.ts +5 -7
  248. package/src/store/resolve-store.ts +100 -0
  249. package/src/store/store-fs.ts +12 -5
  250. package/dist/store/gi/class-struct-record.d.ts +0 -10
  251. package/dist/store/gi/class-struct-record.d.ts.map +0 -1
  252. package/dist/store/gi/class-struct-record.js +0 -52
  253. package/dist/store/gi/class-struct-record.js.map +0 -1
  254. package/src/store/gi/class-struct-record.ts +0 -72
@@ -28,7 +28,7 @@ type SignalMapSpec = {
28
28
  context: ModuleContext;
29
29
  klass: GirClass;
30
30
  className: string;
31
- parentlessExtendsObject: boolean;
31
+ isParentlessObjectSubclass: boolean;
32
32
  suffix: string;
33
33
  renderEntry: (context: ModuleContext, signal: GirCallable) => string;
34
34
  };
@@ -66,7 +66,7 @@ const renderSignalMembers = (context: ModuleContext, klass: GirClass): string[]
66
66
 
67
67
  const connectDefault = isRootObject
68
68
  ? "default:\n throw new globalThis.Error(\"Unknown signal '\" + signal + \"'\");"
69
- : "default:\n return super.connect(signal, handler, after);";
69
+ : "default:\n return super.connect(signal, handler, isAfter);";
70
70
 
71
71
  const emitDefault = isRootObject
72
72
  ? "default:\n throw new globalThis.Error(\"Unknown signal '\" + sigName + \"'\");"
@@ -78,7 +78,10 @@ const renderSignalMembers = (context: ModuleContext, klass: GirClass): string[]
78
78
  const emitSwitch = `switch (getSignalBaseName(sigName)) {\n${emitBody}\n}`;
79
79
 
80
80
  return [
81
- renderBlock(`connect(signal: string, handler: ${SIGNAL_HANDLER_TYPE}, after?: boolean): number`, connectSwitch),
81
+ renderBlock(
82
+ `connect(signal: string, handler: ${SIGNAL_HANDLER_TYPE}, isAfter?: boolean): number`,
83
+ connectSwitch,
84
+ ),
82
85
  renderBlock("emit(sigName: string, ...args: unknown[]): unknown", emitSwitch),
83
86
  ];
84
87
  };
@@ -89,7 +92,7 @@ const renderSignalDeclarations = (
89
92
  className: string,
90
93
  isParentlessObjectSubclass: boolean,
91
94
  ): string[] => {
92
- const base = { context, klass, className, parentlessExtendsObject: isParentlessObjectSubclass };
95
+ const base = { context, klass, className, isParentlessObjectSubclass };
93
96
 
94
97
  const declarations = [
95
98
  renderSignalMap({ ...base, suffix: SIGNALS_SUFFIX, renderEntry: renderSignalHandlerType }),
@@ -110,8 +113,8 @@ const renderSignalDeclarations = (
110
113
  };
111
114
 
112
115
  const renderSignalMap = (spec: SignalMapSpec): string => {
113
- const { context, klass, className, parentlessExtendsObject, suffix, renderEntry } = spec;
114
- const extendsRefs = signalMapParentRefs(context, klass, parentlessExtendsObject, suffix);
116
+ const { context, klass, className, isParentlessObjectSubclass, suffix, renderEntry } = spec;
117
+ const extendsRefs = signalMapParentRefs(context, klass, isParentlessObjectSubclass, suffix);
115
118
  const extendsClause = extendsRefs.length === 0 ? "" : ` extends ${extendsRefs.join(", ")}`;
116
119
 
117
120
  const signalEntries = collectClassSignals(context, klass).map((signal) => {
@@ -177,8 +180,8 @@ const renderSignalConnectInterface = (className: string, isRootObject: boolean):
177
180
 
178
181
  const lines = [
179
182
  `__signals__?: ${map};`,
180
- `connect<K extends keyof ${map}>(signal: K, handler: ${map}[K], after?: boolean): number;`,
181
- `connect(signal: string, handler: ${SIGNAL_HANDLER_TYPE}, after?: boolean): number;`,
183
+ `connect<K extends keyof ${map}>(signal: K, handler: ${map}[K], isAfter?: boolean): number;`,
184
+ `connect(signal: string, handler: ${SIGNAL_HANDLER_TYPE}, isAfter?: boolean): number;`,
182
185
  `emit<K extends keyof ${emitMap}>(sigName: K, ...args: ${emitMap}[K]["args"]): ${emitMap}[K]["result"];`,
183
186
  "emit(sigName: string, ...args: unknown[]): unknown;",
184
187
  ];
@@ -193,7 +196,7 @@ const renderSignalConnectInterface = (className: string, isRootObject: boolean):
193
196
  }
194
197
  };
195
198
 
196
- chainable(["on", "once", "addEventListener"], ", after?: boolean");
199
+ chainable(["on", "once", "addEventListener"], ", isAfter?: boolean");
197
200
  chainable(["off", "removeEventListener"], "");
198
201
  }
199
202
 
@@ -216,8 +219,8 @@ const renderResultType = (
216
219
  library: context.library,
217
220
  signal,
218
221
  renderType: (ref, nullable) => renderTsType(context, ref, nullable),
219
- includeCallerAllocated: shouldIncludeCallerAllocated,
220
- optOut: isOptOut,
222
+ shouldIncludeCallerAllocated,
223
+ isOptOut,
221
224
  });
222
225
 
223
226
  const renderSignalEmitEntry = (context: ModuleContext, signal: GirCallable): string => {
@@ -237,7 +240,7 @@ const nonVarargParameters = (signal: GirCallable): GirParameter[] =>
237
240
 
238
241
  const renderConnectCase = (context: ModuleContext, signal: GirCallable): string => {
239
242
  const callback = renderCallback(context, signal);
240
- const body = `return connectSignal(this, signal, { callback: ${callback}, handler, after: after ?? false });`;
243
+ const body = `return connectSignal(this, signal, { callback: ${callback}, handler, isAfter: isAfter ?? false });`;
241
244
 
242
245
  return renderBlock(`case ${sourceStringLiteral(signal.name)}:`, body);
243
246
  };
@@ -260,7 +263,7 @@ const renderEmitArgLiteral = (options: EmitArgOptions): { literal: string; nextA
260
263
  const value = renderCallerOutAllocation(context, parameter);
261
264
 
262
265
  return {
263
- literal: `{ type: ${descriptor}, direction: "out", callerAllocated: true, value: ${value} }`,
266
+ literal: `{ type: ${descriptor}, direction: "out", isCallerAllocated: true, value: ${value} }`,
264
267
  nextArgIndex: argIndex,
265
268
  };
266
269
  }
@@ -268,7 +271,7 @@ const renderEmitArgLiteral = (options: EmitArgOptions): { literal: string; nextA
268
271
  if (isRecordInout(context, parameter)) {
269
272
  return {
270
273
  literal:
271
- `{ type: ${descriptor}, direction: "inout", callerAllocated: true, ` +
274
+ `{ type: ${descriptor}, direction: "inout", isCallerAllocated: true, ` +
272
275
  `value: args[${String(argIndex)}] }`,
273
276
  nextArgIndex: argIndex + 1,
274
277
  };
@@ -328,9 +331,6 @@ const renderCallerOutAllocation = (context: ModuleContext, parameter: GirParamet
328
331
  const renderCallback = (context: ModuleContext, signal: GirCallable): string => {
329
332
  const params = nonVarargParameters(signal);
330
333
 
331
- // The emitted argument list prepends the instance the signal was raised on, so every index a
332
- // parameter descriptor carries (an array's `sizeParamIndex`, above all) sits one slot further
333
- // right than the position GIR assigned it.
334
334
  const callbackParamDescriptors = params.map((parameter) =>
335
335
  renderParamDescriptor(context, parameter, parameter.type, { argIndexOffset: 1 }),
336
336
  );
@@ -0,0 +1,87 @@
1
+ import type { GirField } from "../../gir/field.js";
2
+ import type { PrimitiveCategory } from "../../gir/primitives.js";
3
+ import type { GirRecord } from "../../gir/record.js";
4
+ import type { TypeId } from "../../gir/type-id.js";
5
+ import type { GirType } from "../../gir/type.js";
6
+ import type { ModuleContext } from "../../writer/context.js";
7
+
8
+ type Scope = { context: ModuleContext; seen: Set<string> };
9
+
10
+ const POINTER_CATEGORIES: Set<PrimitiveCategory> = new Set<PrimitiveCategory>(["string", "pointer"]);
11
+
12
+ const recordKey = (namespaceName: string, record: GirRecord): string => `${namespaceName}.${record.name}`;
13
+
14
+ const hasOwnCopySemantics = (record: GirRecord): boolean =>
15
+ record.glibGetType !== undefined ||
16
+ ((record.glibRefFunc ?? record.copyFunc) !== undefined && (record.glibUnrefFunc ?? record.freeFunc) !== undefined);
17
+
18
+ const isValueSafeArray = (scope: Scope, type: Extract<GirType, { kind: "carray" }>): boolean =>
19
+ type.fixedSize !== undefined && isValueSafeRef(scope, type.element, type.elementCType);
20
+
21
+ const isValueSafeAlias = (scope: Scope, type: Extract<GirType, { kind: "alias" }>): boolean =>
22
+ isValueSafeRef(scope, type.value.target, type.value.targetCType);
23
+
24
+ function isValueSafeType(scope: Scope, type: GirType): boolean {
25
+ switch (type.kind) {
26
+ case "primitive": {
27
+ return !POINTER_CATEGORIES.has(type.category);
28
+ }
29
+ case "enum": {
30
+ return true;
31
+ }
32
+ case "record": {
33
+ return isValueSafeRecord(scope, type.namespace.name, type.value);
34
+ }
35
+ case "carray": {
36
+ return isValueSafeArray(scope, type);
37
+ }
38
+ case "alias": {
39
+ return isValueSafeAlias(scope, type);
40
+ }
41
+ case "callback":
42
+ case "class":
43
+ case "hashtable":
44
+ case "interface":
45
+ case "list":
46
+ case "varargs": {
47
+ return false;
48
+ }
49
+ }
50
+ }
51
+
52
+ function isValueSafeRef(scope: Scope, ref: TypeId | undefined, cType: string | undefined): boolean {
53
+ if (ref === undefined || cType?.endsWith("*") === true) {
54
+ return false;
55
+ }
56
+
57
+ const type = scope.context.library.typeFor(ref);
58
+
59
+ return type === undefined ? false : isValueSafeType(scope, type);
60
+ }
61
+
62
+ const isValueSafeField = (scope: Scope, field: GirField): boolean =>
63
+ field.inlineMembers === undefined
64
+ ? isValueSafeRef(scope, field.type, field.cType)
65
+ : field.inlineMembers.every((member) => isValueSafeField(scope, member));
66
+
67
+ function isValueSafeRecord(scope: Scope, namespaceName: string, record: GirRecord): boolean {
68
+ const key = recordKey(namespaceName, record);
69
+
70
+ if (scope.seen.has(key) || record.fields.length === 0) {
71
+ return false;
72
+ }
73
+
74
+ scope.seen.add(key);
75
+ const isSafe = record.fields.every((field) => isValueSafeField(scope, field));
76
+ scope.seen.delete(key);
77
+
78
+ return isSafe;
79
+ }
80
+
81
+ const isValueMarshalable = (context: ModuleContext, namespaceName: string, record: GirRecord): boolean =>
82
+ isValueSafeRecord({ context, seen: new Set<string>() }, namespaceName, record);
83
+
84
+ const isConstructibleRecord = (context: ModuleContext, namespaceName: string, record: GirRecord): boolean =>
85
+ hasOwnCopySemantics(record) || isValueMarshalable(context, namespaceName, record);
86
+
87
+ export { hasOwnCopySemantics, isConstructibleRecord, isValueMarshalable };
@@ -4,8 +4,6 @@ import { fileURLToPath } from "node:url";
4
4
  import { FINGERPRINT_FILENAME, type GiFingerprint } from "../fingerprint.js";
5
5
  import { buildManifest, type StoreOptions, subpathExport, writeStore } from "./store-fs.js";
6
6
 
7
- type GiStoreOptions = StoreOptions;
8
-
9
7
  type GiNamespaceInput = {
10
8
  directory: string;
11
9
  rawSource: string;
@@ -69,7 +67,7 @@ const collectStoreSources = (
69
67
  };
70
68
 
71
69
  const writeGiStore = (
72
- options: GiStoreOptions,
70
+ options: StoreOptions,
73
71
  namespaces: GiNamespaceInput[],
74
72
  fingerprint: GiFingerprint,
75
73
  ): void => {
@@ -89,4 +87,4 @@ const writeGiStore = (
89
87
  });
90
88
  };
91
89
 
92
- export { writeGiStore, type GiStoreOptions, type GiNamespaceInput };
90
+ export { writeGiStore, type GiNamespaceInput };
@@ -6,9 +6,7 @@ import type { LazyElementSpec } from "./element-prop-types.js";
6
6
  import { renderJsDoc } from "../../writer/doc.js";
7
7
  import { ancestorGlibNames, type GlibNamedClass } from "./intrinsic-elements.js";
8
8
 
9
- /** A component that wraps a generated element, keyed by GLib type name (built-in or user-provided). */
10
9
  type ElementComponent = { module: string; export: string };
11
- /** Component wrappers keyed by GLib type name; the framework's built-ins merged with the project's own. */
12
10
  type ElementComponentOverrides = Record<string, ElementComponent>;
13
11
 
14
12
  type ExportCollector = {
@@ -73,9 +71,6 @@ const appendCandidateExport = (
73
71
  collector.exportedNames.add(candidate.glibName);
74
72
  };
75
73
 
76
- // An abstract GType cannot be instantiated: `g_object_new` on one is a fatal GObject error rather
77
- // than a NULL return. Its props interface and metadata still have to be emitted, since every
78
- // concrete subclass extends them, so only the constructible component export is withheld.
79
74
  const collectCandidateExports = (collector: ExportCollector, options: CandidateExportOptions): void => {
80
75
  for (const candidate of options.intrinsicElements) {
81
76
  if (candidate.klass.isAbstract) {
@@ -121,7 +116,6 @@ const renderCandidateExport = (
121
116
  return `${renderJsDoc(klass.doc)}${renderElementComponentExport(glibName, component)}`;
122
117
  };
123
118
 
124
- /** The component for the nearest ancestry match, most-derived first; user overrides win on their exact type. */
125
119
  const resolveElementComponent = (
126
120
  ancestry: string[],
127
121
  components: Record<string, ElementComponent>,
@@ -1,11 +1,15 @@
1
- /** Props interface a generated element's props extend, declared by hand in `@gtkx/react`. */
2
1
  type ElementPropTypeRef = { type: string; module: string; export: string };
2
+
3
3
  /** Base props interface each element extends, keyed by GLib type name (the module exports it). */
4
- type ElementProps = Record<string, { module: string; export: string }>;
4
+ type ElementProps = Record<string, {
5
+ /** Specifier the props interface is imported from. */
6
+ module: string;
7
+ /** Identifier the module exports it under. */
8
+ export: string;
9
+ }>;
5
10
 
6
11
  const elementProps: Map<string, { module: string; export: string }> = new Map();
7
12
 
8
- /** Installs the base props each element extends (GLib type name → `{ module, export }`); set once per run. */
9
13
  const setElementProps = (props: ElementProps): void => {
10
14
  elementProps.clear();
11
15
 
@@ -14,7 +18,6 @@ const setElementProps = (props: ElementProps): void => {
14
18
  }
15
19
  };
16
20
 
17
- /** The base props interface an element extends, when its element config declares one. */
18
21
  const elementPropTypeFor = (glibName: string): ElementPropTypeRef | undefined => {
19
22
  const ref = elementProps.get(glibName);
20
23
 
@@ -37,7 +37,6 @@ const createLazyElementSpec = (context: GirIndex, element: string, entry: GirTyp
37
37
  return { element, typeName, typeSource: `export type ${typeName} = ${base} & { children?: ReactNode };` };
38
38
  };
39
39
 
40
- /** Lazy element prop types to emit, grouped by the namespace declaring each element. */
41
40
  const lazyElementSpecs = (context: GirIndex, lazyElements: string[]): Map<string, LazyElementSpec[]> => {
42
41
  const specs: Map<string, LazyElementSpec[]> = new Map();
43
42
 
@@ -0,0 +1,60 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import type { GlibNamedClass } from "./intrinsic-elements.js";
4
+ import { namespaceDirectory } from "../../gir/namespace.js";
5
+
6
+ /** One element the `@gtkx/jsx` store binds. */
7
+ type GeneratedElement = {
8
+ /** GIR namespace name, such as `"Gtk"`. */
9
+ namespace: string;
10
+ /** Subexport the element is reachable through, such as `"gtk"` in `@gtkx/jsx/gtk`. */
11
+ directory: string;
12
+ /** GLib type name, which is also the JSX tag name, such as `"GtkButton"`. */
13
+ glibName: string;
14
+ /**
15
+ * Whether the element can be rendered. An abstract GType is bound for its props and metadata but
16
+ * exports no component, because `g_object_new` on one is a fatal GObject error.
17
+ */
18
+ isMountable: boolean;
19
+ };
20
+
21
+ const ELEMENTS_FILENAME = "elements.json";
22
+
23
+ const collectGeneratedElements = (intrinsicElements: GlibNamedClass[]): GeneratedElement[] =>
24
+ intrinsicElements
25
+ .map((entry) => ({
26
+ namespace: entry.namespace.name,
27
+ directory: namespaceDirectory(entry.namespace),
28
+ glibName: entry.glibName,
29
+ isMountable: !entry.klass.isAbstract,
30
+ }))
31
+ .toSorted((a, b) => a.glibName.localeCompare(b.glibName));
32
+
33
+ const renderGeneratedElements = (elements: GeneratedElement[]): string =>
34
+ `${JSON.stringify(elements, null, 2)}\n`;
35
+
36
+ /**
37
+ * Reads the inventory of elements the `@gtkx/jsx` store binds, written into the store by codegen. Answers
38
+ * "what does this project's JSX layer cover" without loading the store, which resolves `virtual:` specifiers
39
+ * that only exist inside a Vite or Vitest build.
40
+ *
41
+ * @param jsxStoreDir The jsx store directory, as given by `resolveStore(projectRoot).jsx.storeDir`.
42
+ * @returns Every bound element, sorted by GLib type name, or an empty array when the store has no inventory.
43
+ */
44
+ const readGeneratedElements = (jsxStoreDir: string): GeneratedElement[] => {
45
+ const path = join(jsxStoreDir, ELEMENTS_FILENAME);
46
+
47
+ if (!existsSync(path)) {
48
+ return [];
49
+ }
50
+
51
+ return JSON.parse(readFileSync(path, "utf8")) as GeneratedElement[];
52
+ };
53
+
54
+ export {
55
+ ELEMENTS_FILENAME,
56
+ collectGeneratedElements,
57
+ readGeneratedElements,
58
+ renderGeneratedElements,
59
+ type GeneratedElement,
60
+ };
@@ -214,14 +214,6 @@ const hasMatchingAncestorName = (
214
214
  isMatch: (glibName: string) => boolean,
215
215
  ): boolean => hasMatchingAncestor(klass, namespace, library, (_klass, glibName) => isMatch(glibName));
216
216
 
217
- const hasExposedMethod = (
218
- klass: GirClass,
219
- namespace: GirNamespace,
220
- library: Library,
221
- methodName: string,
222
- ): boolean =>
223
- hasMatchingAncestor(klass, namespace, library, (current) => current.methods.some((m) => m.name === methodName));
224
-
225
217
  const isIntrinsicElementClass = (klass: GirClass, namespace: GirNamespace, library: Library): boolean =>
226
218
  hasMatchingAncestorName(klass, namespace, library, (glibName) => glibName === "GObject");
227
219
 
@@ -253,7 +245,6 @@ export {
253
245
  newlyImplementedInterfaces,
254
246
  collectInterfacePropsClasses,
255
247
  ancestorGlibNames,
256
- hasExposedMethod,
257
248
  isIntrinsicElementClass,
258
249
  collectIntrinsicElementClasses,
259
250
  type GlibNamedClass,
@@ -109,7 +109,7 @@ const generateJsxSection = (
109
109
  addReactBuiltin(imports, "ReactNode");
110
110
  addReactBuiltin(imports, "Ref");
111
111
  const interfaceResult = renderInterfacePropBlocks(library, targetNamespace.name, options);
112
- let isNeedsReactElement = interfaceResult.needsReactElement;
112
+ let requiresReactElement = interfaceResult.requiresReactElement;
113
113
  const propBlocks: string[] = [...interfaceResult.blocks];
114
114
 
115
115
  const blockContext: RenderPropBlockContext = {
@@ -124,13 +124,13 @@ const generateJsxSection = (
124
124
  const { block, objectPropNames } = renderPropBlock(library, entry, blockContext);
125
125
 
126
126
  if (objectPropNames.length > 0) {
127
- isNeedsReactElement = true;
127
+ requiresReactElement = true;
128
128
  }
129
129
 
130
130
  propBlocks.push(block);
131
131
  }
132
132
 
133
- if (isNeedsReactElement) {
133
+ if (requiresReactElement) {
134
134
  addReactBuiltin(imports, "ReactElement");
135
135
  }
136
136
 
@@ -152,11 +152,11 @@ const renderInterfacePropBlocks = (
152
152
  library: Library,
153
153
  targetNamespaceName: string,
154
154
  options: GenerateJsxOptions,
155
- ): { blocks: string[]; needsReactElement: boolean; hasContainerProps: HasContainerProps } => {
155
+ ): { blocks: string[]; requiresReactElement: boolean; hasContainerProps: HasContainerProps } => {
156
156
  const { imports, intrinsicElements } = options;
157
157
  const context: InterfaceBlockContext = { library, targetNamespaceName, imports, hasContainerProps };
158
158
  const blocks: string[] = [];
159
- let isNeedsReactElement = false;
159
+ let requiresReactElement = false;
160
160
 
161
161
  for (const iface of collectInterfacePropsClasses(
162
162
  library,
@@ -173,13 +173,13 @@ const renderInterfacePropBlocks = (
173
173
  const { block, objectPropNames } = renderInterfacePropsBlock(iface, glibName, context);
174
174
 
175
175
  if (objectPropNames.length > 0) {
176
- isNeedsReactElement = true;
176
+ requiresReactElement = true;
177
177
  }
178
178
 
179
179
  blocks.push(block);
180
180
  }
181
181
 
182
- return { blocks, needsReactElement: isNeedsReactElement, hasContainerProps };
182
+ return { blocks, requiresReactElement, hasContainerProps };
183
183
  };
184
184
 
185
185
  const registerCrossNsProps = (
@@ -23,7 +23,7 @@ type IntrinsicElementEntry = {
23
23
  };
24
24
 
25
25
  type PropNameCollector = {
26
- keep: (property: GirProperty) => boolean;
26
+ shouldKeep: (property: GirProperty) => boolean;
27
27
  seen: Set<string>;
28
28
  names: string[];
29
29
  };
@@ -57,10 +57,10 @@ const generateMetadata = (library: Library): string => {
57
57
  .map(({ glibName, defaults }) => ` "${glibName}": ${renderDefaultsObject(defaults)},`);
58
58
 
59
59
  return `${[
60
- `export const SIGNALS: Record<string, Record<string, string>> = {\n${signalsEntries.join("\n")}\n};`,
61
- `export const CONSTRUCT_ONLY_PROPS: Record<string, Set<string>> = {\n${constructOnlyEntries.join("\n")}\n};`,
62
- `export const CONSTRUCT_PROPS: Record<string, Set<string>> = {\n${constructableEntries.join("\n")}\n};`,
63
- `export const DEFAULT_PROPS: Record<string, Record<string, unknown>> = {\n${defaultsEntries.join("\n")}\n};`,
60
+ `export const signals: Record<string, Record<string, string>> = {\n${signalsEntries.join("\n")}\n};`,
61
+ `export const constructOnlyProps: Record<string, Set<string>> = {\n${constructOnlyEntries.join("\n")}\n};`,
62
+ `export const constructProps: Record<string, Set<string>> = {\n${constructableEntries.join("\n")}\n};`,
63
+ `export const defaultProps: Record<string, Record<string, unknown>> = {\n${defaultsEntries.join("\n")}\n};`,
64
64
  ].join("\n\n")}\n`;
65
65
  };
66
66
 
@@ -114,10 +114,10 @@ const collectSignals = (sources: GirClass[]): [string, string][] => {
114
114
  };
115
115
 
116
116
  const collectPropNamesFromSource = (source: GirClass, collector: PropNameCollector): void => {
117
- const { keep, seen, names } = collector;
117
+ const { shouldKeep, seen, names } = collector;
118
118
 
119
119
  for (const property of source.properties) {
120
- if (!keep(property)) {
120
+ if (!shouldKeep(property)) {
121
121
  continue;
122
122
  }
123
123
 
@@ -133,7 +133,7 @@ const collectPropNamesFromSource = (source: GirClass, collector: PropNameCollect
133
133
  };
134
134
 
135
135
  const collectPropNames = (sources: GirClass[], shouldKeep: (property: GirProperty) => boolean): string[] => {
136
- const collector: PropNameCollector = { keep: shouldKeep, seen: new Set<string>(), names: [] };
136
+ const collector: PropNameCollector = { shouldKeep, seen: new Set<string>(), names: [] };
137
137
 
138
138
  for (const source of sources) {
139
139
  collectPropNamesFromSource(source, collector);
@@ -0,0 +1,30 @@
1
+ /** Props left out of the generated element props, keyed by GLib type name. */
2
+ type OmittedProps = Record<string, string[]>;
3
+
4
+ const omittedProps: Map<string, Set<string>> = new Map();
5
+
6
+ const setOmittedProps = (props: OmittedProps): void => {
7
+ omittedProps.clear();
8
+
9
+ for (const [glibName, names] of Object.entries(props)) {
10
+ omittedProps.set(glibName, new Set(names));
11
+ }
12
+ };
13
+
14
+ const isOmittedProp = (glibName: string | undefined, jsName: string): boolean =>
15
+ glibName !== undefined && omittedProps.get(glibName)?.has(jsName) === true;
16
+
17
+ /** Merges omitted-prop maps keyed by GLib type name, concatenating the names each map contributes. */
18
+ const mergeOmittedProps = (...maps: OmittedProps[]): OmittedProps => {
19
+ const merged: OmittedProps = {};
20
+
21
+ for (const map of maps) {
22
+ for (const [glibName, names] of Object.entries(map)) {
23
+ merged[glibName] = [...(merged[glibName] ?? []), ...names];
24
+ }
25
+ }
26
+
27
+ return merged;
28
+ };
29
+
30
+ export { setOmittedProps, isOmittedProp, mergeOmittedProps, type OmittedProps };
@@ -5,10 +5,12 @@ import { ImportsBuilder } from "../../writer/imports.js";
5
5
  import { type ElementComponentOverrides, generateElementComponentsSection } from "./element-components.js";
6
6
  import { type ElementProps, setElementProps } from "./element-prop-imports.js";
7
7
  import { type LazyElementSpec, lazyElementSpecs } from "./element-prop-types.js";
8
+ import { collectGeneratedElements, type GeneratedElement } from "./generated-elements.js";
8
9
  import { buildGirIndex } from "./gir-index.js";
9
10
  import { collectIntrinsicElementClasses, type GlibNamedClass } from "./intrinsic-elements.js";
10
11
  import { generateJsxSection } from "./jsx.js";
11
12
  import { generateMetadata } from "./metadata.js";
13
+ import { type OmittedProps, setOmittedProps } from "./omitted-props.js";
12
14
 
13
15
  type JsxNamespaceFile = {
14
16
  directory: string;
@@ -19,12 +21,14 @@ type JsxFiles = {
19
21
  namespaces: JsxNamespaceFile[];
20
22
  metadata: string;
21
23
  intrinsicElementCount: number;
24
+ elements: GeneratedElement[];
22
25
  };
23
26
 
24
27
  type JsxGenerationOptions = {
25
28
  reactSubexports?: string[];
26
29
  components?: ElementComponentOverrides;
27
30
  props?: ElementProps;
31
+ omittedProps?: OmittedProps;
28
32
  lazyElements?: string[];
29
33
  };
30
34
 
@@ -47,6 +51,7 @@ type JsxNamespaceContext = {
47
51
 
48
52
  const generateJsxFiles = (library: Library, options: JsxGenerationOptions = {}): JsxFiles => {
49
53
  setElementProps(options.props ?? {});
54
+ setOmittedProps(options.omittedProps ?? {});
50
55
  const intrinsicElements = collectIntrinsicElementClasses(library);
51
56
  const intrinsicElementByGlibName = new Map(intrinsicElements.map((entry) => [entry.glibName, entry]));
52
57
  const girIndex = buildGirIndex(library);
@@ -63,7 +68,7 @@ const generateJsxFiles = (library: Library, options: JsxGenerationOptions = {}):
63
68
 
64
69
  const metadata = generateMetadata(library);
65
70
 
66
- return { namespaces, metadata, intrinsicElementCount };
71
+ return { namespaces, metadata, intrinsicElementCount, elements: collectGeneratedElements(intrinsicElements) };
67
72
  };
68
73
 
69
74
  const orderedIntrinsicNamespaces = (intrinsicElements: GlibNamedClass[]): GirNamespace[] => {
@@ -9,12 +9,8 @@ import { renderHandlerParameters, renderHandlerResultType } from "../../analysis
9
9
  import { recordTypeTarget, renderBaseType, type TsTypeTarget } from "../../analysis/ts-type.js";
10
10
  import { type GirProperty, isConstructableProperty } from "../../gir/property.js";
11
11
  import { renderJsDoc } from "../../writer/doc.js";
12
- import {
13
- giNamespaceAlias,
14
- hasExposedMethod,
15
- isIntrinsicElementClass,
16
- signalHandlerName,
17
- } from "./intrinsic-elements.js";
12
+ import { getGlibName, giNamespaceAlias, isIntrinsicElementClass, signalHandlerName } from "./intrinsic-elements.js";
13
+ import { isOmittedProp } from "./omitted-props.js";
18
14
 
19
15
  type IntrinsicElementPropsEntries = {
20
16
  propLines: string[];
@@ -75,7 +71,7 @@ const appendPropertyLines = (state: PropCollectorState, property: GirProperty, j
75
71
  const tsType = renderReactPropType(state.types, property.type, false);
76
72
  const doc = renderJsDoc(property.doc);
77
73
 
78
- if (isObjectProp(state.owner, property, jsName)) {
74
+ if (isObjectProp(state.owner.library, property)) {
79
75
  state.propLines.push(`${doc}${jsName}?: ${tsType} | ReactElement | null | undefined;`);
80
76
  state.objectPropNames.push(jsName);
81
77
 
@@ -99,7 +95,7 @@ const acceptCollectorProperty = (state: PropCollectorState, property: GirPropert
99
95
 
100
96
  const jsName = toCamelIdentifier(property.name);
101
97
 
102
- if (state.seen.has(jsName)) {
98
+ if (state.seen.has(jsName) || isOmittedProp(getGlibName(state.owner.klass), jsName)) {
103
99
  return;
104
100
  }
105
101
 
@@ -215,20 +211,12 @@ const isGObjectType = (library: Library, ref: TypeId | undefined): boolean => {
215
211
  return isIntrinsicElementClass(resolved.value, resolved.namespace, library);
216
212
  };
217
213
 
218
- const isObjectProp = (owner: IntrinsicElementScope, property: GirProperty, jsName: string): boolean => {
214
+ const isObjectProp = (library: Library, property: GirProperty): boolean => {
219
215
  if (!property.writable || property.constructOnly) {
220
216
  return false;
221
217
  }
222
218
 
223
- if (!isGObjectType(owner.library, property.type)) {
224
- return false;
225
- }
226
-
227
- if (jsName === "child" && hasExposedMethod(owner.klass, owner.namespace, owner.library, "set_child")) {
228
- return false;
229
- }
230
-
231
- return true;
219
+ return isGObjectType(library, property.type);
232
220
  };
233
221
 
234
222
  const renderSignalHandler = (options: SignalRenderOptions): string => {
@@ -243,8 +231,8 @@ const renderSignalHandler = (options: SignalRenderOptions): string => {
243
231
  library: types.library,
244
232
  signal,
245
233
  renderType: (ref, nullable) => renderReactPropType(types, ref, nullable),
246
- includeCallerAllocated: false,
247
- optOut: true,
234
+ shouldIncludeCallerAllocated: false,
235
+ isOptOut: true,
248
236
  });
249
237
 
250
238
  return `(${params.join(", ")}) => ${result}`;
@@ -2,13 +2,11 @@ import type { JsxNamespaceFile } from "./jsx/pipeline.js";
2
2
  import type { RawFile } from "./store-fs.js";
3
3
  import { buildManifest, type StoreOptions, subpathExport, writeStore } from "./store-fs.js";
4
4
 
5
- type JsxStoreOptions = StoreOptions;
6
-
7
5
  const writeJsxStore = (
8
- options: JsxStoreOptions,
6
+ options: StoreOptions,
9
7
  namespaces: JsxNamespaceFile[],
10
8
  metadata: string,
11
- fingerprint: RawFile,
9
+ rawFiles: RawFile[],
12
10
  ): void => {
13
11
  const exportsMap: Record<string, unknown> = {
14
12
  "./metadata": subpathExport("metadata"),
@@ -31,9 +29,9 @@ const writeJsxStore = (
31
29
  exports: exportsMap,
32
30
  peerDependencies: { "@gtkx/gi": "*", "@gtkx/react": "*", react: "*" },
33
31
  }),
34
- rawFiles: [fingerprint],
35
- configEnv: true,
32
+ rawFiles,
33
+ requiresEnvReference: true,
36
34
  });
37
35
  };
38
36
 
39
- export { writeJsxStore, type JsxStoreOptions };
37
+ export { writeJsxStore };