@gtkx/codegen 1.2.2 → 1.4.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 (218) hide show
  1. package/README.md +6 -4
  2. package/dist/analysis/descriptor-render.d.ts +7 -1
  3. package/dist/analysis/descriptor-render.d.ts.map +1 -1
  4. package/dist/analysis/descriptor-render.js +37 -15
  5. package/dist/analysis/descriptor-render.js.map +1 -1
  6. package/dist/analysis/descriptor.d.ts +5 -1
  7. package/dist/analysis/descriptor.d.ts.map +1 -1
  8. package/dist/analysis/descriptor.js +12 -2
  9. package/dist/analysis/descriptor.js.map +1 -1
  10. package/dist/analysis/param-capability.d.ts +5 -2
  11. package/dist/analysis/param-capability.d.ts.map +1 -1
  12. package/dist/analysis/param-capability.js +36 -4
  13. package/dist/analysis/param-capability.js.map +1 -1
  14. package/dist/analysis/param-structure.d.ts +1 -1
  15. package/dist/analysis/param-structure.d.ts.map +1 -1
  16. package/dist/analysis/param-structure.js.map +1 -1
  17. package/dist/analysis/ts-type.d.ts +1 -1
  18. package/dist/analysis/ts-type.d.ts.map +1 -1
  19. package/dist/analysis/ts-type.js +28 -10
  20. package/dist/analysis/ts-type.js.map +1 -1
  21. package/dist/compile.js +1 -1
  22. package/dist/compile.js.map +1 -1
  23. package/dist/docs/api-reference.d.ts +6 -0
  24. package/dist/docs/api-reference.d.ts.map +1 -1
  25. package/dist/docs/api-reference.js +11 -2
  26. package/dist/docs/api-reference.js.map +1 -1
  27. package/dist/docs/pipeline.d.ts +3 -0
  28. package/dist/docs/pipeline.d.ts.map +1 -1
  29. package/dist/docs/pipeline.js +10 -1
  30. package/dist/docs/pipeline.js.map +1 -1
  31. package/dist/fingerprint.d.ts +5 -0
  32. package/dist/fingerprint.d.ts.map +1 -1
  33. package/dist/fingerprint.js +111 -15
  34. package/dist/fingerprint.js.map +1 -1
  35. package/dist/gi.d.ts +3 -0
  36. package/dist/gi.d.ts.map +1 -1
  37. package/dist/gi.js +33 -7
  38. package/dist/gi.js.map +1 -1
  39. package/dist/gir/external-namespaces.d.ts +8 -0
  40. package/dist/gir/external-namespaces.d.ts.map +1 -0
  41. package/dist/gir/external-namespaces.js +4 -0
  42. package/dist/gir/external-namespaces.js.map +1 -0
  43. package/dist/gir/finish-overrides.d.ts +3 -0
  44. package/dist/gir/finish-overrides.d.ts.map +1 -0
  45. package/dist/gir/finish-overrides.js +5 -0
  46. package/dist/gir/finish-overrides.js.map +1 -0
  47. package/dist/gir/function.d.ts +5 -2
  48. package/dist/gir/function.d.ts.map +1 -1
  49. package/dist/gir/function.js +34 -5
  50. package/dist/gir/function.js.map +1 -1
  51. package/dist/gir/hidden-symbols.d.ts +3 -0
  52. package/dist/gir/hidden-symbols.d.ts.map +1 -0
  53. package/dist/gir/hidden-symbols.js +39 -0
  54. package/dist/gir/hidden-symbols.js.map +1 -0
  55. package/dist/gir/library.d.ts +22 -2
  56. package/dist/gir/library.d.ts.map +1 -1
  57. package/dist/gir/library.js +21 -3
  58. package/dist/gir/library.js.map +1 -1
  59. package/dist/gir/ucs4-overrides.d.ts +3 -0
  60. package/dist/gir/ucs4-overrides.d.ts.map +1 -0
  61. package/dist/gir/ucs4-overrides.js +7 -0
  62. package/dist/gir/ucs4-overrides.js.map +1 -0
  63. package/dist/guards.d.ts +2 -1
  64. package/dist/guards.d.ts.map +1 -1
  65. package/dist/guards.js +3 -1
  66. package/dist/guards.js.map +1 -1
  67. package/dist/index.d.ts +2 -0
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +2 -0
  70. package/dist/index.js.map +1 -1
  71. package/dist/jsx.d.ts.map +1 -1
  72. package/dist/jsx.js +27 -8
  73. package/dist/jsx.js.map +1 -1
  74. package/dist/runner.d.ts +8 -0
  75. package/dist/runner.d.ts.map +1 -1
  76. package/dist/runner.js +9 -3
  77. package/dist/runner.js.map +1 -1
  78. package/dist/store/external-package-links.d.ts +6 -0
  79. package/dist/store/external-package-links.d.ts.map +1 -0
  80. package/dist/store/external-package-links.js +158 -0
  81. package/dist/store/external-package-links.js.map +1 -0
  82. package/dist/store/gi/callable-doc.d.ts.map +1 -1
  83. package/dist/store/gi/callable-doc.js +4 -4
  84. package/dist/store/gi/callable-doc.js.map +1 -1
  85. package/dist/store/gi/callables.js +1 -1
  86. package/dist/store/gi/callables.js.map +1 -1
  87. package/dist/store/gi/class.d.ts.map +1 -1
  88. package/dist/store/gi/class.js +17 -0
  89. package/dist/store/gi/class.js.map +1 -1
  90. package/dist/store/gi/constructor-props.js +1 -1
  91. package/dist/store/gi/constructor-props.js.map +1 -1
  92. package/dist/store/gi/function.d.ts.map +1 -1
  93. package/dist/store/gi/function.js +2 -1
  94. package/dist/store/gi/function.js.map +1 -1
  95. package/dist/store/gi/generated-libraries.d.ts +26 -0
  96. package/dist/store/gi/generated-libraries.d.ts.map +1 -0
  97. package/dist/store/gi/generated-libraries.js +53 -0
  98. package/dist/store/gi/generated-libraries.js.map +1 -0
  99. package/dist/store/gi/gtype-binding.d.ts +2 -1
  100. package/dist/store/gi/gtype-binding.d.ts.map +1 -1
  101. package/dist/store/gi/gtype-binding.js +6 -1
  102. package/dist/store/gi/gtype-binding.js.map +1 -1
  103. package/dist/store/gi/method.d.ts +7 -2
  104. package/dist/store/gi/method.d.ts.map +1 -1
  105. package/dist/store/gi/method.js +126 -30
  106. package/dist/store/gi/method.js.map +1 -1
  107. package/dist/store/gi/param-marshal.d.ts +8 -1
  108. package/dist/store/gi/param-marshal.d.ts.map +1 -1
  109. package/dist/store/gi/param-marshal.js +52 -4
  110. package/dist/store/gi/param-marshal.js.map +1 -1
  111. package/dist/store/gi/record-constructor.d.ts.map +1 -1
  112. package/dist/store/gi/record-constructor.js +2 -3
  113. package/dist/store/gi/record-constructor.js.map +1 -1
  114. package/dist/store/gi/record-field-accessor.d.ts +2 -1
  115. package/dist/store/gi/record-field-accessor.d.ts.map +1 -1
  116. package/dist/store/gi/record-field-accessor.js +9 -3
  117. package/dist/store/gi/record-field-accessor.js.map +1 -1
  118. package/dist/store/gi/runtime-override.js +1 -1
  119. package/dist/store/gi/runtime-override.js.map +1 -1
  120. package/dist/store/gi/signal.js +2 -2
  121. package/dist/store/gi/signal.js.map +1 -1
  122. package/dist/store/gi/vtable.d.ts.map +1 -1
  123. package/dist/store/gi/vtable.js +82 -12
  124. package/dist/store/gi/vtable.js.map +1 -1
  125. package/dist/store/gi-store.d.ts +12 -2
  126. package/dist/store/gi-store.d.ts.map +1 -1
  127. package/dist/store/gi-store.js +24 -5
  128. package/dist/store/gi-store.js.map +1 -1
  129. package/dist/store/jsx/jsx.d.ts.map +1 -1
  130. package/dist/store/jsx/jsx.js +3 -1
  131. package/dist/store/jsx/jsx.js.map +1 -1
  132. package/dist/store/jsx/metadata.d.ts.map +1 -1
  133. package/dist/store/jsx/metadata.js +35 -60
  134. package/dist/store/jsx/metadata.js.map +1 -1
  135. package/dist/store/jsx-store.d.ts +8 -1
  136. package/dist/store/jsx-store.d.ts.map +1 -1
  137. package/dist/store/jsx-store.js +9 -2
  138. package/dist/store/jsx-store.js.map +1 -1
  139. package/dist/store/resolve-store.d.ts +2 -1
  140. package/dist/store/resolve-store.d.ts.map +1 -1
  141. package/dist/store/resolve-store.js +3 -1
  142. package/dist/store/resolve-store.js.map +1 -1
  143. package/dist/store/store-fs.d.ts +2 -1
  144. package/dist/store/store-fs.d.ts.map +1 -1
  145. package/dist/store/store-fs.js +8 -3
  146. package/dist/store/store-fs.js.map +1 -1
  147. package/dist/writer/context.d.ts.map +1 -1
  148. package/dist/writer/context.js +8 -0
  149. package/dist/writer/context.js.map +1 -1
  150. package/dist/writer/gtk-doc.d.ts.map +1 -1
  151. package/dist/writer/gtk-doc.js +27 -1
  152. package/dist/writer/gtk-doc.js.map +1 -1
  153. package/overrides/gdk/rgba.ts.ejs +15 -1
  154. package/overrides/glib/index.ts.ejs +3 -0
  155. package/overrides/glib/regex.ts.ejs +66 -0
  156. package/overrides/gobject/index.ts.ejs +3 -0
  157. package/overrides/gobject/object-class.ts.ejs +27 -0
  158. package/overrides/gobject/param-spec-getters.ts.ejs +57 -0
  159. package/overrides/gobject/param-spec.ts.ejs +21 -0
  160. package/overrides/gobject/value.ts.ejs +6 -1
  161. package/overrides/graphene/structs.ts.ejs +38 -3
  162. package/overrides/gtk/index.ts.ejs +2 -0
  163. package/overrides/gtk/widget-class.ts.ejs +28 -0
  164. package/package.json +7 -6
  165. package/src/analysis/descriptor-render.ts +60 -16
  166. package/src/analysis/descriptor.ts +17 -3
  167. package/src/analysis/param-capability.ts +68 -5
  168. package/src/analysis/param-structure.ts +1 -1
  169. package/src/analysis/ts-type.ts +45 -11
  170. package/src/compile.ts +1 -1
  171. package/src/docs/api-reference.ts +20 -2
  172. package/src/docs/pipeline.ts +15 -1
  173. package/src/fingerprint.ts +151 -16
  174. package/src/gi.ts +48 -9
  175. package/src/gir/external-namespaces.ts +8 -0
  176. package/src/gir/finish-overrides.ts +5 -0
  177. package/src/gir/function.ts +52 -14
  178. package/src/gir/hidden-symbols.ts +39 -0
  179. package/src/gir/library.ts +36 -3
  180. package/src/gir/ucs4-overrides.ts +7 -0
  181. package/src/guards.ts +6 -1
  182. package/src/index.ts +2 -0
  183. package/src/jsx.ts +33 -8
  184. package/src/runner.ts +17 -3
  185. package/src/store/external-package-links.ts +226 -0
  186. package/src/store/gi/callable-doc.ts +8 -4
  187. package/src/store/gi/callables.ts +1 -1
  188. package/src/store/gi/class.ts +29 -0
  189. package/src/store/gi/constructor-props.ts +1 -1
  190. package/src/store/gi/function.ts +2 -1
  191. package/src/store/gi/generated-libraries.ts +91 -0
  192. package/src/store/gi/gtype-binding.ts +8 -1
  193. package/src/store/gi/method.ts +189 -36
  194. package/src/store/gi/param-marshal.ts +67 -4
  195. package/src/store/gi/record-constructor.ts +8 -3
  196. package/src/store/gi/record-field-accessor.ts +11 -2
  197. package/src/store/gi/runtime-override.ts +1 -1
  198. package/src/store/gi/signal.ts +2 -2
  199. package/src/store/gi/vtable.ts +118 -11
  200. package/src/store/gi-store.ts +48 -5
  201. package/src/store/jsx/jsx.ts +3 -1
  202. package/src/store/jsx/metadata.ts +46 -99
  203. package/src/store/jsx-store.ts +23 -7
  204. package/src/store/resolve-store.ts +3 -1
  205. package/src/store/store-fs.ts +10 -2
  206. package/src/writer/context.ts +11 -0
  207. package/src/writer/gtk-doc.ts +38 -1
  208. package/overrides/cairo/context.ts.ejs +0 -1269
  209. package/overrides/cairo/font-face.ts.ejs +0 -151
  210. package/overrides/cairo/font-options.ts.ejs +0 -199
  211. package/overrides/cairo/image-surface.ts.ejs +0 -132
  212. package/overrides/cairo/index.ts.ejs +0 -22
  213. package/overrides/cairo/matrix.ts.ejs +0 -171
  214. package/overrides/cairo/pattern.ts.ejs +0 -476
  215. package/overrides/cairo/recording-surface.ts.ejs +0 -102
  216. package/overrides/cairo/region.ts.ejs +0 -237
  217. package/overrides/cairo/scaled-font.ts.ejs +0 -330
  218. package/overrides/cairo/surface.ts.ejs +0 -296
@@ -24,6 +24,18 @@ type TypeTable = {
24
24
  index: Map<string, number>;
25
25
  };
26
26
 
27
+ /** Behavior a project opted into ahead of the next major version, through the `future` block of its config. */
28
+ type FutureBehavior = {
29
+ /** Whether byte sequences render as `Uint8Array` rather than `number[]`. */
30
+ isByteArrayTyped?: boolean;
31
+ /** Whether a `GObject.Value` a binding hands back surfaces as what it holds rather than as the value. */
32
+ isValueUnwrapped?: boolean;
33
+ /** Whether a promisified result drops the leading success boolean of a throwing finish function. */
34
+ isFinishTrimmed?: boolean;
35
+ /** Whether a handle-passing inout parameter is mutated in place rather than packed into the result. */
36
+ isInoutInPlace?: boolean;
37
+ };
38
+
27
39
  /** A namespace whose header has been read and whose shell is registered, before its body is parsed. */
28
40
  type DiscoveredNamespace = {
29
41
  /** Attributes and includes read from the namespace's GIR file. */
@@ -84,12 +96,15 @@ class Library {
84
96
  *
85
97
  * @param libraries GIR identifiers to load, such as `Gtk-4.0`.
86
98
  * @param girPath Directories searched, in order, for each `.gir` file.
87
- * @param isByteArrayTyped Whether byte sequences render as `Uint8Array` rather than `number[]`.
99
+ * @param future Behavior the project opted into ahead of the next major version.
88
100
  * @returns A library holding every namespace that was parsed.
89
101
  */
90
- static load(libraries: string[], girPath: string[], isByteArrayTyped = false): Library {
102
+ static load(libraries: string[], girPath: string[], future: FutureBehavior = {}): Library {
91
103
  const library = new Library();
92
- library.isByteArrayTypedValue = isByteArrayTyped;
104
+ library.isByteArrayTypedValue = future.isByteArrayTyped === true;
105
+ library.isValueUnwrappedValue = future.isValueUnwrapped === true;
106
+ library.isFinishTrimmedValue = future.isFinishTrimmed === true;
107
+ library.isInoutInPlaceValue = future.isInoutInPlace === true;
93
108
  this.drive(library, libraries, girPath);
94
109
 
95
110
  return library;
@@ -102,6 +117,9 @@ class Library {
102
117
  private typeTables: TypeTable[] = [];
103
118
  private girFilesValue: string[] = [];
104
119
  private isByteArrayTypedValue = false;
120
+ private isValueUnwrappedValue = false;
121
+ private isFinishTrimmedValue = false;
122
+ private isInoutInPlaceValue = false;
105
123
 
106
124
  /** Creates a library with no namespaces loaded; {@link Library.load} builds a populated one. */
107
125
  constructor() {
@@ -336,6 +354,21 @@ class Library {
336
354
  return this.isByteArrayTypedValue;
337
355
  }
338
356
 
357
+ /** Whether a `GObject.Value` a binding hands back surfaces as what it holds rather than as the value. */
358
+ public get isValueUnwrapped(): boolean {
359
+ return this.isValueUnwrappedValue;
360
+ }
361
+
362
+ /** Whether a promisified result drops the leading success boolean of a throwing finish function. */
363
+ public get isFinishTrimmed(): boolean {
364
+ return this.isFinishTrimmedValue;
365
+ }
366
+
367
+ /** Whether a handle-passing inout parameter is mutated in place rather than packed into the result. */
368
+ public get isInoutInPlace(): boolean {
369
+ return this.isInoutInPlaceValue;
370
+ }
371
+
339
372
  /** Paths of the `.gir` files that were read, in the order they were discovered. */
340
373
  public get girFiles(): string[] {
341
374
  return this.girFilesValue;
@@ -0,0 +1,7 @@
1
+ const RETURNS_MISSING_UCS4_ARRAY_TYPE: Set<string> = new Set([
2
+ "g_utf8_to_ucs4",
3
+ "g_utf8_to_ucs4_fast",
4
+ "g_utf16_to_ucs4",
5
+ ]);
6
+
7
+ export { RETURNS_MISSING_UCS4_ARRAY_TYPE };
package/src/guards.ts CHANGED
@@ -12,6 +12,11 @@ const isString = (value: unknown): value is string => typeof value === "string";
12
12
  const arrayGuard = <T>(isEntry: Guard<T>): Guard<T[]> =>
13
13
  (value: unknown): value is T[] => Array.isArray(value) && value.every((entry: unknown) => isEntry(entry));
14
14
 
15
+ const recordGuard = <T>(isEntry: Guard<T>): Guard<Record<string, T>> =>
16
+ (value: unknown): value is Record<string, T> =>
17
+ isRecord(value) && !Array.isArray(value) &&
18
+ Object.values(value).every((entry: unknown) => isEntry(entry));
19
+
15
20
  const optionalGuard = <T>(isPresent: Guard<T>): OptionalGuard<T> =>
16
21
  Object.assign((value: unknown): value is T | undefined => value === undefined || isPresent(value), {
17
22
  __optional__: true as const,
@@ -20,4 +25,4 @@ const optionalGuard = <T>(isPresent: Guard<T>): OptionalGuard<T> =>
20
25
  const hasFields = <T extends object>(value: unknown, guards: FieldGuards<T>): value is T =>
21
26
  isRecord(value) && Object.entries(guards).every(([name, guard]) => (guard as Guard<unknown>)(value[name]));
22
27
 
23
- export { arrayGuard, hasFields, isBoolean, isNumber, isString, optionalGuard };
28
+ export { arrayGuard, hasFields, isBoolean, isNumber, isString, optionalGuard, recordGuard };
package/src/index.ts CHANGED
@@ -20,6 +20,8 @@ export { type BuiltinElements, type ModuleExport, readBuiltinElements } from "./
20
20
  /** @public */
21
21
  export { type CodegenRunnerOptions, type CodegenRunnerResult, runCodegen } from "./runner.js";
22
22
  /** @public */
23
+ export { type GeneratedLibraries, readGeneratedLibraries } from "./store/gi/generated-libraries.js";
24
+ /** @public */
23
25
  export type { ElementProps } from "./store/jsx/element-prop-imports.js";
24
26
  /** @public */
25
27
  export { type GeneratedElement, readGeneratedElements } from "./store/jsx/generated-elements.js";
package/src/jsx.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  type JsxFingerprintInput,
7
7
  jsxStoreFreshness,
8
8
  } from "./fingerprint.js";
9
+ import { externalPackageFor } from "./gir/external-namespaces.js";
9
10
  import { type ModuleExport, readBuiltinElements } from "./react/element-config.js";
10
11
  import { writeJsxStore } from "./store/jsx-store.js";
11
12
  import { ELEMENTS_FILENAME, renderGeneratedElements } from "./store/jsx/generated-elements.js";
@@ -39,6 +40,7 @@ const runJsxCodegen = async (options: RunJsxCodegenOptions): Promise<RunJsxCodeg
39
40
 
40
41
  const fingerprintInput: JsxFingerprintInput = {
41
42
  reactVersion: options.jsx.version,
43
+ reactSubexports: options.reactSubexports,
42
44
  components,
43
45
  lazyElements,
44
46
  props,
@@ -53,7 +55,9 @@ const runJsxCodegen = async (options: RunJsxCodegenOptions): Promise<RunJsxCodeg
53
55
  }
54
56
  }
55
57
 
56
- const { namespaces, metadata, intrinsicElementCount, elements } = generateJsxFiles(options.getLibrary(), {
58
+ const library = options.getLibrary();
59
+
60
+ const { namespaces, metadata, intrinsicElementCount, elements } = generateJsxFiles(library, {
57
61
  reactSubexports: options.reactSubexports,
58
62
  components,
59
63
  lazyElements,
@@ -61,15 +65,36 @@ const runJsxCodegen = async (options: RunJsxCodegenOptions): Promise<RunJsxCodeg
61
65
  omittedProps,
62
66
  });
63
67
 
64
- writeJsxStore(options.jsx, namespaces, metadata, [
65
- {
66
- relativePath: FINGERPRINT_FILENAME,
67
- content: `${JSON.stringify(computeJsxFingerprint(fingerprintInput, intrinsicElementCount), null, 2)}\n`,
68
- },
69
- { relativePath: ELEMENTS_FILENAME, content: renderGeneratedElements(elements) },
70
- ]);
68
+ writeJsxStore({
69
+ options: options.jsx,
70
+ namespaces,
71
+ metadata,
72
+ externalPackages: storeExternalPackages(library),
73
+ rawFiles: [
74
+ {
75
+ relativePath: FINGERPRINT_FILENAME,
76
+ content:
77
+ `${JSON.stringify(computeJsxFingerprint(fingerprintInput, intrinsicElementCount), null, 2)}\n`,
78
+ },
79
+ { relativePath: ELEMENTS_FILENAME, content: renderGeneratedElements(elements) },
80
+ ],
81
+ });
71
82
 
72
83
  return { isRegenerated: true, intrinsicElementCount };
73
84
  };
74
85
 
86
+ const storeExternalPackages = (library: Library): string[] => {
87
+ const packages: Set<string> = new Set();
88
+
89
+ for (const namespace of library.namespaces.values()) {
90
+ const packageName = externalPackageFor(namespace.name);
91
+
92
+ if (packageName !== undefined) {
93
+ packages.add(packageName);
94
+ }
95
+ }
96
+
97
+ return [...packages];
98
+ };
99
+
75
100
  export { runJsxCodegen };
package/src/runner.ts CHANGED
@@ -8,6 +8,7 @@ import { runGiCodegen } from "./gi.js";
8
8
  import { Library } from "./gir/library.js";
9
9
  import { generateGlModules, type GlGenerationReport } from "./khronos/pipeline.js";
10
10
  import { sweepStagingDirs } from "./staging.js";
11
+ import { externalPackageNotices } from "./store/external-package-links.js";
11
12
  import { ensureStoreLink, type StoreOptions } from "./store/store-fs.js";
12
13
 
13
14
  type GlCodegenOptions = {
@@ -41,6 +42,12 @@ type CodegenRunnerOptions = {
41
42
  isForced?: boolean;
42
43
  /** Emits GIR byte sequences as `Uint8Array` rather than `number[]`; defaults to false. */
43
44
  isByteArrayTyped?: boolean;
45
+ /** Surfaces a `GObject.Value` a binding hands back as what it holds rather than as the value; defaults to false. */
46
+ isValueUnwrapped?: boolean;
47
+ /** Drops the leading success boolean from promisified results of a throwing finish; defaults to false. */
48
+ isFinishTrimmed?: boolean;
49
+ /** Mutates a handle-passing inout parameter in place instead of packing it into the result; defaults to false. */
50
+ isInoutInPlace?: boolean;
44
51
  };
45
52
 
46
53
  /** What a `runCodegen` run produced. */
@@ -51,6 +58,8 @@ type CodegenRunnerResult = {
51
58
  namespaces: number;
52
59
  /** How many JSX elements the jsx store binds, zero when no jsx store was requested. */
53
60
  intrinsicElements: number;
61
+ /** Deprecation notices about how the store was wired, one line each; empty when nothing needs attention. */
62
+ notices: string[];
54
63
  /** Wall-clock duration of the run, in milliseconds. */
55
64
  duration: number;
56
65
  };
@@ -83,6 +92,7 @@ const runCodegen = async (options: CodegenRunnerOptions): Promise<CodegenRunnerR
83
92
  isRegenerated: store.isRegenerated,
84
93
  namespaces: store.namespaces,
85
94
  intrinsicElements: store.intrinsicElements,
95
+ notices: externalPackageNotices(options.gi),
86
96
  duration: Date.now() - start,
87
97
  };
88
98
  };
@@ -160,13 +170,17 @@ const emitStoresWithConfig = async (config: {
160
170
  }): Promise<StoreResult> => {
161
171
  const { options, gi, jsx, libraries, girPath } = config;
162
172
  const isByteArrayTyped = options.isByteArrayTyped === true;
173
+ const isValueUnwrapped = options.isValueUnwrapped === true;
174
+ const isFinishTrimmed = options.isFinishTrimmed === true;
175
+ const isInoutInPlace = options.isInoutInPlace === true;
176
+ const future = { isByteArrayTyped, isValueUnwrapped, isFinishTrimmed, isInoutInPlace };
163
177
  let library: Library | undefined;
164
- const loadLibrary = (): Library => (library ??= Library.load(libraries, girPath, isByteArrayTyped));
165
- const giInputs = { girFiles: [] as string[], libraries, girPath, isByteArrayTyped };
178
+ const loadLibrary = (): Library => (library ??= Library.load(libraries, girPath, future));
179
+ const giInputs = { girFiles: [] as string[], libraries, girPath, storeVersion: gi.version, ...future };
166
180
  const isGiRegenerated = options.isForced === true || !isGiStoreFresh(gi.storeDir, giInputs);
167
181
 
168
182
  const namespaces = isGiRegenerated
169
- ? runGiCodegen(loadLibrary(), { gi, libraries, girPath, isByteArrayTyped })
183
+ ? runGiCodegen(loadLibrary(), { gi, libraries, girPath, ...future })
170
184
  : 0;
171
185
 
172
186
  if (jsx === undefined) {
@@ -0,0 +1,226 @@
1
+ import { existsSync, readFileSync, realpathSync, rmSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join } from "node:path";
4
+ import { EXTERNAL_NAMESPACES, type ExternalNamespace } from "../gir/external-namespaces.js";
5
+ import { nodeModulesChain } from "./resolve-store.js";
6
+ import { type StoreLink, symlinkRelative } from "./store-fs.js";
7
+
8
+ type ExternalPackageSource = "store" | "hoisted" | "codegen";
9
+
10
+ type ExternalPackageResolution = {
11
+ packageName: string;
12
+ directory: string;
13
+ dir: string;
14
+ source: ExternalPackageSource;
15
+ };
16
+
17
+ const DEPENDENCY_FIELDS = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
18
+ const codegenRequire = createRequire(import.meta.url);
19
+ const codegenPackageDirs: Map<string, string | undefined> = new Map();
20
+ const ensuredStores: Set<string> = new Set();
21
+
22
+ const storeAnchor = (link: StoreLink): string => dirname(dirname(link.linkDir));
23
+ const namespaceShimDirectory = (entry: ExternalNamespace): string => entry.namespace.toLowerCase();
24
+
25
+ const resolveFromStore = (link: StoreLink, packageName: string): string | undefined => {
26
+ const chain = nodeModulesChain(dirname(storeAnchor(link)));
27
+
28
+ for (const nodeModules of chain) {
29
+ const manifest = join(nodeModules, packageName, "package.json");
30
+
31
+ if (existsSync(manifest)) {
32
+ return dirname(realpathSync(manifest));
33
+ }
34
+ }
35
+
36
+ return undefined;
37
+ };
38
+
39
+ const projectManifestPath = (link: StoreLink): string => join(dirname(storeAnchor(link)), "package.json");
40
+
41
+ const readManifest = (manifestPath: string): Record<string, unknown> | undefined => {
42
+ try {
43
+ return JSON.parse(readFileSync(manifestPath, "utf8")) as Record<string, unknown>;
44
+ } catch {
45
+ return undefined;
46
+ }
47
+ };
48
+
49
+ const sectionNames = (section: unknown): string[] =>
50
+ typeof section === "object" && section !== null ? Object.keys(section) : [];
51
+
52
+ const declaredDependencies = (manifest: Record<string, unknown>): Set<string> =>
53
+ new Set(DEPENDENCY_FIELDS.flatMap((field) => sectionNames(manifest[field])));
54
+
55
+ const isDeclaredByProject = (link: StoreLink, packageName: string): boolean => {
56
+ const manifest = readManifest(projectManifestPath(link));
57
+
58
+ return manifest === undefined || declaredDependencies(manifest).has(packageName);
59
+ };
60
+
61
+ const resolveFromCodegen = (packageName: string): string | undefined => {
62
+ if (codegenPackageDirs.has(packageName)) {
63
+ return codegenPackageDirs.get(packageName);
64
+ }
65
+
66
+ let dir: string | undefined;
67
+
68
+ try {
69
+ dir = dirname(realpathSync(codegenRequire.resolve(`${packageName}/package.json`)));
70
+ } catch {
71
+ dir = undefined;
72
+ }
73
+
74
+ codegenPackageDirs.set(packageName, dir);
75
+
76
+ return dir;
77
+ };
78
+
79
+ const resolveEntry = (link: StoreLink, entry: ExternalNamespace): ExternalPackageResolution | undefined => {
80
+ const directory = namespaceShimDirectory(entry);
81
+ const chainResolved = resolveFromStore(link, entry.packageName);
82
+
83
+ if (chainResolved !== undefined) {
84
+ const source = isDeclaredByProject(link, entry.packageName) ? "store" : "hoisted";
85
+
86
+ return { packageName: entry.packageName, directory, dir: chainResolved, source };
87
+ }
88
+
89
+ const codegenDir = resolveFromCodegen(entry.packageName);
90
+
91
+ if (codegenDir !== undefined) {
92
+ return { packageName: entry.packageName, directory, dir: codegenDir, source: "codegen" };
93
+ }
94
+
95
+ return undefined;
96
+ };
97
+
98
+ const resolveExternalPackages = (link: StoreLink): ExternalPackageResolution[] => {
99
+ const resolutions: ExternalPackageResolution[] = [];
100
+
101
+ for (const entry of EXTERNAL_NAMESPACES) {
102
+ const resolution = resolveEntry(link, entry);
103
+
104
+ if (resolution !== undefined) {
105
+ resolutions.push(resolution);
106
+ }
107
+ }
108
+
109
+ return resolutions;
110
+ };
111
+
112
+ const isLinkedTo = (linkPath: string, dir: string): boolean => {
113
+ try {
114
+ return realpathSync(linkPath) === realpathSync(dir);
115
+ } catch {
116
+ return false;
117
+ }
118
+ };
119
+
120
+ const applyLink = (storeDir: string, resolution: ExternalPackageResolution): void => {
121
+ const linkPath = join(storeDir, "node_modules", resolution.packageName);
122
+
123
+ if (resolution.source === "codegen") {
124
+ if (!isLinkedTo(linkPath, resolution.dir)) {
125
+ symlinkRelative(linkPath, resolution.dir);
126
+ }
127
+
128
+ return;
129
+ }
130
+
131
+ rmSync(linkPath, { recursive: true, force: true });
132
+ };
133
+
134
+ const tryApplyLink = (storeDir: string, resolution: ExternalPackageResolution): void => {
135
+ try {
136
+ applyLink(storeDir, resolution);
137
+ } catch {
138
+ return;
139
+ }
140
+ };
141
+
142
+ const ensureExternalPackageLinks = (link: StoreLink): void => {
143
+ if (ensuredStores.has(link.storeDir)) {
144
+ return;
145
+ }
146
+
147
+ if (!existsSync(join(link.storeDir, "package.json"))) {
148
+ return;
149
+ }
150
+
151
+ for (const resolution of resolveExternalPackages(link)) {
152
+ tryApplyLink(link.storeDir, resolution);
153
+ }
154
+
155
+ ensuredStores.add(link.storeDir);
156
+ };
157
+
158
+ const resetExternalPackageLinks = (storeDir: string): void => {
159
+ ensuredStores.delete(storeDir);
160
+ };
161
+
162
+ const externalPackageNotice = (resolution: ExternalPackageResolution): string =>
163
+ `${resolution.packageName} is not installed in this project, so the copy @gtkx/codegen depends on was ` +
164
+ `linked into the generated store; add ${resolution.packageName} to your dependencies, because GTKX 2.0 ` +
165
+ "stops doing this.";
166
+
167
+ const hoistedPackageNotice = (resolution: ExternalPackageResolution): string =>
168
+ `${resolution.packageName} is not declared in this project's package.json and only resolves because ` +
169
+ `your package manager hoisted it; add ${resolution.packageName} to your dependencies, because GTKX 2.0 ` +
170
+ "requires the direct dependency.";
171
+
172
+ const hasShim = (storeDir: string, entry: ExternalNamespace): boolean =>
173
+ existsSync(join(storeDir, namespaceShimDirectory(entry), "index.js"));
174
+
175
+ const hasStoreLocalLink = (storeDir: string, packageName: string): boolean =>
176
+ existsSync(join(storeDir, "node_modules", packageName, "package.json"));
177
+
178
+ const unresolvableMessage = (link: StoreLink, entry: ExternalNamespace): string =>
179
+ `Cannot resolve ${entry.packageName} from ${storeAnchor(link)} or from @gtkx/codegen; ` +
180
+ "install it next to @gtkx/runtime.";
181
+
182
+ const linkFailedMessage = (link: StoreLink, entry: ExternalNamespace): string =>
183
+ `Cannot link ${entry.packageName} into the generated store at ${link.storeDir}; ` +
184
+ `install ${entry.packageName} next to @gtkx/runtime.`;
185
+
186
+ const entryNotice = (link: StoreLink, entry: ExternalNamespace): string | undefined => {
187
+ if (!hasShim(link.storeDir, entry)) {
188
+ return undefined;
189
+ }
190
+
191
+ const resolution = resolveEntry(link, entry);
192
+
193
+ if (resolution === undefined) {
194
+ throw new Error(unresolvableMessage(link, entry));
195
+ }
196
+
197
+ if (resolution.source === "store") {
198
+ return undefined;
199
+ }
200
+
201
+ if (resolution.source === "hoisted") {
202
+ return hoistedPackageNotice(resolution);
203
+ }
204
+
205
+ if (!hasStoreLocalLink(link.storeDir, resolution.packageName)) {
206
+ throw new Error(linkFailedMessage(link, entry));
207
+ }
208
+
209
+ return externalPackageNotice(resolution);
210
+ };
211
+
212
+ const externalPackageNotices = (link: StoreLink): string[] => {
213
+ const notices: string[] = [];
214
+
215
+ for (const entry of EXTERNAL_NAMESPACES) {
216
+ const notice = entryNotice(link, entry);
217
+
218
+ if (notice !== undefined) {
219
+ notices.push(notice);
220
+ }
221
+ }
222
+
223
+ return notices;
224
+ };
225
+
226
+ export { ensureExternalPackageLinks, externalPackageNotices, resetExternalPackageLinks };
@@ -12,7 +12,7 @@ import {
12
12
  } from "../../analysis/param-structure.js";
13
13
  import { renderJsDoc } from "../../writer/doc.js";
14
14
  import { annotationSpec, THROWS_TEXT } from "./doc-spec.js";
15
- import { isCallbackParameter, returnedOutParameters } from "./method.js";
15
+ import { isCallbackParameter, returnedOutParameters, shouldTrimFinishBoolean } from "./method.js";
16
16
 
17
17
  type CallableDocOptions = {
18
18
  finishFn?: GirFunction | undefined;
@@ -53,9 +53,9 @@ const tupleReturnsText = (primary: string | undefined, outs: InputParameter[]):
53
53
  return bullets.length === 0 ? undefined : `Tuple of:\n\n${bullets.join("\n")}`;
54
54
  };
55
55
 
56
- const returnsText = (context: ModuleContext, fn: GirFunction): string | undefined => {
56
+ const returnsText = (context: ModuleContext, fn: GirFunction, isPrimaryTrimmed: boolean): string | undefined => {
57
57
  const outs = returnedOutParameters(context, fn);
58
- const isPrimaryOmitted = shouldOmitPrimaryReturn(context.library, fn.returnValue);
58
+ const isPrimaryOmitted = isPrimaryTrimmed || shouldOmitPrimaryReturn(context.library, fn.returnValue);
59
59
  const primary = isPrimaryOmitted ? undefined : fn.returnValue.doc;
60
60
 
61
61
  if (outs.length === 0) {
@@ -92,7 +92,11 @@ const callableSpec = (context: ModuleContext, callable: GirFunction, options: Ca
92
92
  (parameter) => options.finishFn !== undefined && isCallbackParameter(context, parameter),
93
93
  options.renames,
94
94
  ),
95
- returns: returnsText(context, options.finishFn ?? callable),
95
+ returns: returnsText(
96
+ context,
97
+ options.finishFn ?? callable,
98
+ options.finishFn !== undefined && shouldTrimFinishBoolean(context, options.finishFn),
99
+ ),
96
100
  throws: canCallableThrow(callable, options.finishFn) ? THROWS_TEXT : undefined,
97
101
  });
98
102
 
@@ -358,7 +358,7 @@ const renderPromisifiedCallable = (
358
358
  const body = renderPromisifiedBody(
359
359
  context,
360
360
  callable,
361
- finishCallExpression(callable, finishFn, member.ownerName),
361
+ { fn: finishFn, expression: finishCallExpression(callable, finishFn, member.ownerName) },
362
362
  toCamelIdentifier(cIdentifier),
363
363
  );
364
364
 
@@ -247,6 +247,35 @@ const appendClassRegistrations = (context: ModuleContext, klass: GirClass, class
247
247
  gtypeExpr,
248
248
  vfuncs: renderVfuncMetadata(context, klass),
249
249
  });
250
+
251
+ appendClassStructRegistration(context, klass, className, gtypeExpr);
252
+ };
253
+
254
+ const appendClassStructRegistration = (
255
+ context: ModuleContext,
256
+ klass: GirClass,
257
+ className: string,
258
+ gtypeExpr: string | undefined,
259
+ ): void => {
260
+ const typeStruct = klass.glibTypeStruct;
261
+
262
+ if (gtypeExpr === undefined || typeStruct === undefined) {
263
+ return;
264
+ }
265
+
266
+ const resolved = context.library.resolveType(context.namespace.name, typeStruct);
267
+
268
+ if (resolved?.kind !== "record" || !isEmittableEntity(resolved.value)) {
269
+ return;
270
+ }
271
+
272
+ const structName = sanitizeTypeIdentifier(resolved.value.name);
273
+ context.addRuntimeImport("registerClassStruct");
274
+
275
+ context.module.appendRegistration(`registerClassStruct(${className}, ${structName});`, [
276
+ className,
277
+ structName,
278
+ ]);
250
279
  };
251
280
 
252
281
  const addAncestorInterfaceKeys = (context: ModuleContext, ancestor: ResolvedAncestor, keys: Set<string>): void => {
@@ -76,7 +76,7 @@ const renderRootConstructor = (context: ModuleContext): string => {
76
76
  context.addRuntimeTypeImport("AnyClass");
77
77
  context.addRuntimeImport("getClassType");
78
78
  context.addRuntimeImport("newObjectWithProperties");
79
- const body = "newObjectWithProperties(getClassType(this.constructor as AnyClass), props, this);";
79
+ const body = "return newObjectWithProperties(getClassType(this.constructor as AnyClass), props, this);";
80
80
 
81
81
  return renderBlock("constructor(props: object = {})", body);
82
82
  };
@@ -48,6 +48,7 @@ const renderFnExpression = (context: ModuleContext, fn: GirFunction): string | u
48
48
  args: arrayLiteral(params),
49
49
  returns: returnPlan.descriptor,
50
50
  isReturnSkipped: returnPlan.isSkipped,
51
+ isReturnUnpacked: returnPlan.isUnpacked,
51
52
  canThrow: fn.throws,
52
53
  });
53
54
  };
@@ -132,7 +133,7 @@ const matchNamespaceFinish = (context: ModuleContext, fn: GirFunction): Namespac
132
133
  const renderPromisifiedNamespaceFunction = (options: NamespaceFunctionOptions, finish: NamespaceFinish): string => {
133
134
  const { context, fn, exportName, bindingName } = options;
134
135
  const { signature, returnType } = renderPromisifiedSignature(context, fn, finish.fn);
135
- const body = renderPromisifiedBody(context, fn, finish.exportName, bindingName);
136
+ const body = renderPromisifiedBody(context, fn, { fn: finish.fn, expression: finish.exportName }, bindingName);
136
137
 
137
138
  return renderBlock(`export function ${exportName}(${signature}): ${returnType}`, body);
138
139
  };
@@ -0,0 +1,91 @@
1
+ import { sortStrings } from "@gtkx/utils";
2
+ import { join } from "node:path";
3
+ import type { GirNamespace } from "../../gir/namespace.js";
4
+ import { arrayGuard, hasFields, isString, recordGuard } from "../../guards.js";
5
+ import { readJsonFile } from "../../json.js";
6
+
7
+ /** What a generated `@gtkx/gi` store binds, and the library releases it was generated against. */
8
+ type GeneratedLibraries = {
9
+ /** GIR identifiers the store binds, such as `Gtk-4.0`, expanded from the project's `libraries`. */
10
+ libraries: string[];
11
+ /**
12
+ * `MAJOR.MINOR` release, keyed by GIR identifier. A library whose GIR declares no `MAJOR_VERSION`
13
+ * and `MINOR_VERSION` is absent, so an entry is present only for those that name their release.
14
+ */
15
+ versions: Record<string, string>;
16
+ };
17
+
18
+ const LIBRARIES_FILENAME = "libraries.json";
19
+ const MAJOR_CONSTANT = "MAJOR_VERSION";
20
+ const MINOR_CONSTANT = "MINOR_VERSION";
21
+ const DIGITS = /^\d+$/;
22
+
23
+ const constantValue = (namespace: GirNamespace, name: string): string | undefined => {
24
+ const constant = namespace.constants.find((entry) => entry.name === name);
25
+
26
+ return constant !== undefined && DIGITS.test(constant.value) ? constant.value : undefined;
27
+ };
28
+
29
+ const namespaceVersion = (namespace: GirNamespace): string | undefined => {
30
+ const major = constantValue(namespace, MAJOR_CONSTANT);
31
+ const minor = constantValue(namespace, MINOR_CONSTANT);
32
+
33
+ return major === undefined || minor === undefined ? undefined : `${major}.${minor}`;
34
+ };
35
+
36
+ const getNamespaceName = (library: string): string => {
37
+ const separator = library.indexOf("-");
38
+
39
+ return separator === -1 ? library : library.slice(0, separator);
40
+ };
41
+
42
+ const collectGeneratedLibraries = (
43
+ namespaces: Map<string, GirNamespace>,
44
+ libraries: string[],
45
+ ): GeneratedLibraries => {
46
+ const sorted = sortStrings(libraries);
47
+ const versions: Record<string, string> = {};
48
+
49
+ for (const library of sorted) {
50
+ const namespace = namespaces.get(getNamespaceName(library));
51
+ const version = namespace === undefined ? undefined : namespaceVersion(namespace);
52
+
53
+ if (version !== undefined) {
54
+ versions[library] = version;
55
+ }
56
+ }
57
+
58
+ return { libraries: sorted, versions };
59
+ };
60
+
61
+ const renderGeneratedLibraries = (generated: GeneratedLibraries): string =>
62
+ `${JSON.stringify(generated, null, 2)}\n`;
63
+
64
+ const isGeneratedLibraries = (value: unknown): value is GeneratedLibraries =>
65
+ hasFields<GeneratedLibraries>(value, {
66
+ libraries: arrayGuard(isString),
67
+ versions: recordGuard(isString),
68
+ });
69
+
70
+ /**
71
+ * Reads which GIR libraries a generated `@gtkx/gi` store binds and which release of each its GIR declared,
72
+ * out of the inventory codegen writes into the store. Answers "what does this project bind, and how new does
73
+ * the installed library have to be" without parsing a `.gir` file or querying pkg-config.
74
+ *
75
+ * @param giStoreDir The gi store directory, as given by `resolveStore(projectRoot).gi.storeDir`.
76
+ * @returns What the store recorded, or null when it holds no inventory codegen wrote, which covers an absent,
77
+ * unreadable, unparseable and structurally foreign store file alike.
78
+ */
79
+ const readGeneratedLibraries = (giStoreDir: string): GeneratedLibraries | null => {
80
+ const parsed = readJsonFile(join(giStoreDir, LIBRARIES_FILENAME));
81
+
82
+ return isGeneratedLibraries(parsed) ? parsed : null;
83
+ };
84
+
85
+ export {
86
+ LIBRARIES_FILENAME,
87
+ collectGeneratedLibraries,
88
+ type GeneratedLibraries,
89
+ readGeneratedLibraries,
90
+ renderGeneratedLibraries,
91
+ };