@gtkx/codegen 1.2.1 → 1.3.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 (213) hide show
  1. package/README.md +2 -1
  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 +36 -15
  5. package/dist/analysis/descriptor-render.js.map +1 -1
  6. package/dist/analysis/descriptor.d.ts +4 -1
  7. package/dist/analysis/descriptor.d.ts.map +1 -1
  8. package/dist/analysis/descriptor.js +11 -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 +107 -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 +29 -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/jsx.d.ts.map +1 -1
  64. package/dist/jsx.js +27 -8
  65. package/dist/jsx.js.map +1 -1
  66. package/dist/runner.d.ts +8 -0
  67. package/dist/runner.d.ts.map +1 -1
  68. package/dist/runner.js +9 -3
  69. package/dist/runner.js.map +1 -1
  70. package/dist/store/external-package-links.d.ts +6 -0
  71. package/dist/store/external-package-links.d.ts.map +1 -0
  72. package/dist/store/external-package-links.js +158 -0
  73. package/dist/store/external-package-links.js.map +1 -0
  74. package/dist/store/gi/callable-doc.d.ts.map +1 -1
  75. package/dist/store/gi/callable-doc.js +4 -4
  76. package/dist/store/gi/callable-doc.js.map +1 -1
  77. package/dist/store/gi/callables.d.ts +1 -1
  78. package/dist/store/gi/callables.d.ts.map +1 -1
  79. package/dist/store/gi/callables.js +4 -4
  80. package/dist/store/gi/callables.js.map +1 -1
  81. package/dist/store/gi/class.d.ts.map +1 -1
  82. package/dist/store/gi/class.js +25 -15
  83. package/dist/store/gi/class.js.map +1 -1
  84. package/dist/store/gi/constructor-props.js +1 -1
  85. package/dist/store/gi/constructor-props.js.map +1 -1
  86. package/dist/store/gi/function.d.ts.map +1 -1
  87. package/dist/store/gi/function.js +2 -1
  88. package/dist/store/gi/function.js.map +1 -1
  89. package/dist/store/gi/gtype-binding.d.ts +2 -2
  90. package/dist/store/gi/gtype-binding.d.ts.map +1 -1
  91. package/dist/store/gi/gtype-binding.js +6 -13
  92. package/dist/store/gi/gtype-binding.js.map +1 -1
  93. package/dist/store/gi/interface.js +2 -2
  94. package/dist/store/gi/interface.js.map +1 -1
  95. package/dist/store/gi/method.d.ts +7 -2
  96. package/dist/store/gi/method.d.ts.map +1 -1
  97. package/dist/store/gi/method.js +126 -30
  98. package/dist/store/gi/method.js.map +1 -1
  99. package/dist/store/gi/param-marshal.d.ts +8 -1
  100. package/dist/store/gi/param-marshal.d.ts.map +1 -1
  101. package/dist/store/gi/param-marshal.js +52 -4
  102. package/dist/store/gi/param-marshal.js.map +1 -1
  103. package/dist/store/gi/record-constructor.d.ts.map +1 -1
  104. package/dist/store/gi/record-constructor.js +2 -3
  105. package/dist/store/gi/record-constructor.js.map +1 -1
  106. package/dist/store/gi/record-field-accessor.d.ts +2 -1
  107. package/dist/store/gi/record-field-accessor.d.ts.map +1 -1
  108. package/dist/store/gi/record-field-accessor.js +142 -16
  109. package/dist/store/gi/record-field-accessor.js.map +1 -1
  110. package/dist/store/gi/record-layout.d.ts +5 -2
  111. package/dist/store/gi/record-layout.d.ts.map +1 -1
  112. package/dist/store/gi/record-layout.js +2 -1
  113. package/dist/store/gi/record-layout.js.map +1 -1
  114. package/dist/store/gi/record.d.ts.map +1 -1
  115. package/dist/store/gi/record.js +9 -5
  116. package/dist/store/gi/record.js.map +1 -1
  117. package/dist/store/gi/runtime-override.js +1 -1
  118. package/dist/store/gi/runtime-override.js.map +1 -1
  119. package/dist/store/gi/signal.js +2 -2
  120. package/dist/store/gi/signal.js.map +1 -1
  121. package/dist/store/gi/vtable.d.ts.map +1 -1
  122. package/dist/store/gi/vtable.js +82 -12
  123. package/dist/store/gi/vtable.js.map +1 -1
  124. package/dist/store/gi-store.d.ts +7 -2
  125. package/dist/store/gi-store.d.ts.map +1 -1
  126. package/dist/store/gi-store.js +19 -4
  127. package/dist/store/gi-store.js.map +1 -1
  128. package/dist/store/jsx/jsx.d.ts.map +1 -1
  129. package/dist/store/jsx/jsx.js +3 -1
  130. package/dist/store/jsx/jsx.js.map +1 -1
  131. package/dist/store/jsx-store.d.ts +8 -1
  132. package/dist/store/jsx-store.d.ts.map +1 -1
  133. package/dist/store/jsx-store.js +9 -2
  134. package/dist/store/jsx-store.js.map +1 -1
  135. package/dist/store/resolve-store.d.ts +2 -1
  136. package/dist/store/resolve-store.d.ts.map +1 -1
  137. package/dist/store/resolve-store.js +2 -1
  138. package/dist/store/resolve-store.js.map +1 -1
  139. package/dist/store/store-fs.d.ts +2 -1
  140. package/dist/store/store-fs.d.ts.map +1 -1
  141. package/dist/store/store-fs.js +8 -3
  142. package/dist/store/store-fs.js.map +1 -1
  143. package/dist/writer/context.d.ts.map +1 -1
  144. package/dist/writer/context.js +8 -0
  145. package/dist/writer/context.js.map +1 -1
  146. package/dist/writer/gtk-doc.d.ts.map +1 -1
  147. package/dist/writer/gtk-doc.js +27 -1
  148. package/dist/writer/gtk-doc.js.map +1 -1
  149. package/overrides/gdk/rgba.ts.ejs +15 -1
  150. package/overrides/glib/index.ts.ejs +3 -0
  151. package/overrides/glib/regex.ts.ejs +66 -0
  152. package/overrides/gobject/index.ts.ejs +3 -0
  153. package/overrides/gobject/object-class.ts.ejs +27 -0
  154. package/overrides/gobject/param-spec-getters.ts.ejs +57 -0
  155. package/overrides/gobject/param-spec.ts.ejs +21 -0
  156. package/overrides/gobject/value.ts.ejs +6 -1
  157. package/overrides/graphene/structs.ts.ejs +38 -3
  158. package/overrides/gtk/index.ts.ejs +2 -0
  159. package/overrides/gtk/widget-class.ts.ejs +28 -0
  160. package/package.json +6 -5
  161. package/src/analysis/descriptor-render.ts +59 -16
  162. package/src/analysis/descriptor.ts +15 -3
  163. package/src/analysis/param-capability.ts +68 -5
  164. package/src/analysis/param-structure.ts +1 -1
  165. package/src/analysis/ts-type.ts +45 -11
  166. package/src/compile.ts +1 -1
  167. package/src/docs/api-reference.ts +20 -2
  168. package/src/docs/pipeline.ts +15 -1
  169. package/src/fingerprint.ts +144 -16
  170. package/src/gi.ts +44 -9
  171. package/src/gir/external-namespaces.ts +8 -0
  172. package/src/gir/finish-overrides.ts +5 -0
  173. package/src/gir/function.ts +52 -14
  174. package/src/gir/hidden-symbols.ts +39 -0
  175. package/src/gir/library.ts +36 -3
  176. package/src/gir/ucs4-overrides.ts +7 -0
  177. package/src/jsx.ts +33 -8
  178. package/src/runner.ts +17 -3
  179. package/src/store/external-package-links.ts +226 -0
  180. package/src/store/gi/callable-doc.ts +8 -4
  181. package/src/store/gi/callables.ts +5 -5
  182. package/src/store/gi/class.ts +42 -32
  183. package/src/store/gi/constructor-props.ts +1 -1
  184. package/src/store/gi/function.ts +2 -1
  185. package/src/store/gi/gtype-binding.ts +7 -15
  186. package/src/store/gi/interface.ts +2 -2
  187. package/src/store/gi/method.ts +189 -36
  188. package/src/store/gi/param-marshal.ts +67 -4
  189. package/src/store/gi/record-constructor.ts +8 -3
  190. package/src/store/gi/record-field-accessor.ts +209 -16
  191. package/src/store/gi/record-layout.ts +11 -1
  192. package/src/store/gi/record.ts +17 -13
  193. package/src/store/gi/runtime-override.ts +1 -1
  194. package/src/store/gi/signal.ts +2 -2
  195. package/src/store/gi/vtable.ts +118 -11
  196. package/src/store/gi-store.ts +33 -3
  197. package/src/store/jsx/jsx.ts +3 -1
  198. package/src/store/jsx-store.ts +23 -7
  199. package/src/store/resolve-store.ts +2 -1
  200. package/src/store/store-fs.ts +10 -2
  201. package/src/writer/context.ts +11 -0
  202. package/src/writer/gtk-doc.ts +38 -1
  203. package/overrides/cairo/context.ts.ejs +0 -1269
  204. package/overrides/cairo/font-face.ts.ejs +0 -151
  205. package/overrides/cairo/font-options.ts.ejs +0 -199
  206. package/overrides/cairo/image-surface.ts.ejs +0 -132
  207. package/overrides/cairo/index.ts.ejs +0 -22
  208. package/overrides/cairo/matrix.ts.ejs +0 -171
  209. package/overrides/cairo/pattern.ts.ejs +0 -476
  210. package/overrides/cairo/recording-surface.ts.ejs +0 -102
  211. package/overrides/cairo/region.ts.ejs +0 -237
  212. package/overrides/cairo/scaled-font.ts.ejs +0 -330
  213. package/overrides/cairo/surface.ts.ejs +0 -296
@@ -1,9 +1,10 @@
1
- import { sortStrings } from "@gtkx/utils";
2
- import { createHash } from "node:crypto";
1
+ import { createHash, type Hash } from "node:crypto";
3
2
  import { type Dirent, existsSync, readdirSync, readFileSync } from "node:fs";
4
- import { dirname, join, resolve } from "node:path";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, join, relative, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import packageManifest from "../package.json" with { type: "json" };
7
+ import { EXTERNAL_NAMESPACES } from "./gir/external-namespaces.js";
7
8
  import { arrayGuard, hasFields, isNumber, isString, optionalGuard } from "./guards.js";
8
9
  import { readJsonFile } from "./json.js";
9
10
 
@@ -11,7 +12,11 @@ type GiInputs = {
11
12
  girFiles: string[];
12
13
  libraries: string[];
13
14
  girPath: string[];
15
+ storeVersion: string | undefined;
14
16
  isByteArrayTyped: boolean;
17
+ isValueUnwrapped: boolean;
18
+ isFinishTrimmed: boolean;
19
+ isInoutInPlace: boolean;
15
20
  };
16
21
 
17
22
  type GiFingerprint = {
@@ -36,6 +41,7 @@ type DocsFingerprint = {
36
41
 
37
42
  type JsxFingerprintInput = {
38
43
  reactVersion: string;
44
+ reactSubexports: string[];
39
45
  components: Record<string, ModuleExport>;
40
46
  lazyElements: string[];
41
47
  props: Record<string, ModuleExport>;
@@ -47,25 +53,139 @@ type JsxFingerprint = {
47
53
  intrinsicElementCount: number;
48
54
  };
49
55
 
56
+ type InstalledManifest = { name: string; version: string };
57
+
50
58
  const FINGERPRINT_FILENAME = ".codegen-fingerprint.json";
51
- const CODEGEN_VERSION: string = packageManifest.version;
52
- const OVERRIDES_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "overrides");
59
+ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
60
+ const OVERRIDES_ROOT = join(PACKAGE_ROOT, "overrides");
61
+ const requireFromCodegen = createRequire(import.meta.url);
62
+ const codegenHashCache: Map<string, string> = new Map();
63
+
64
+ const compareOrdinal = (a: string, b: string): number => {
65
+ if (a < b) {
66
+ return -1;
67
+ }
68
+
69
+ return a > b ? 1 : 0;
70
+ };
53
71
 
54
- const sortAlpha = (values: string[]): string => sortStrings(values).join(",");
72
+ const sortOrdinal = (values: string[]): string[] => values.toSorted(compareOrdinal);
73
+ const sortAlpha = (values: string[]): string => sortOrdinal(values).join(",");
55
74
 
56
- const overrideFiles = (): string[] => {
57
- if (!existsSync(OVERRIDES_ROOT)) {
75
+ const filesUnder = (root: string): string[] => {
76
+ if (!existsSync(root)) {
58
77
  return [];
59
78
  }
60
79
 
61
- return readdirSync(OVERRIDES_ROOT, { recursive: true, withFileTypes: true })
80
+ return readdirSync(root, { recursive: true, withFileTypes: true })
62
81
  .filter((entry: Dirent) => entry.isFile())
63
82
  .map((entry: Dirent) => join(entry.parentPath, entry.name));
64
83
  };
65
84
 
85
+ const hashTree = (hash: Hash, label: string, root: string): void => {
86
+ const files = sortOrdinal(filesUnder(root));
87
+
88
+ for (const file of files) {
89
+ hash.update("\n");
90
+ hash.update(join(label, relative(root, file)));
91
+ hash.update("\0");
92
+ hash.update(readFileSync(file));
93
+ }
94
+ };
95
+
96
+ const isInstalledManifest = (value: unknown): value is InstalledManifest =>
97
+ hasFields<InstalledManifest>(value, { name: isString, version: isString });
98
+
99
+ const tryResolve = (specifier: string): string | undefined => {
100
+ try {
101
+ return requireFromCodegen.resolve(specifier);
102
+ } catch {
103
+ return undefined;
104
+ }
105
+ };
106
+
107
+ const manifestVersion = (path: string | undefined, name: string): string | undefined => {
108
+ if (path === undefined) {
109
+ return undefined;
110
+ }
111
+
112
+ const manifest = readJsonFile(path);
113
+
114
+ return isInstalledManifest(manifest) && manifest.name === name ? manifest.version : undefined;
115
+ };
116
+
117
+ const packageRootFor = (name: string): string | undefined => {
118
+ const entry = tryResolve(name);
119
+
120
+ if (entry === undefined) {
121
+ return undefined;
122
+ }
123
+
124
+ for (let directory = dirname(entry); directory !== dirname(directory); directory = dirname(directory)) {
125
+ if (manifestVersion(join(directory, "package.json"), name) !== undefined) {
126
+ return directory;
127
+ }
128
+ }
129
+
130
+ return undefined;
131
+ };
132
+
133
+ const installedVersion = (name: string): string | undefined => {
134
+ const direct = manifestVersion(tryResolve(`${name}/package.json`), name);
135
+
136
+ if (direct !== undefined) {
137
+ return direct;
138
+ }
139
+
140
+ const root = packageRootFor(name);
141
+
142
+ return root === undefined ? undefined : manifestVersion(join(root, "package.json"), name);
143
+ };
144
+
145
+ const dependencyVersions = (): string[] =>
146
+ sortOrdinal(
147
+ Object.entries(packageManifest.dependencies)
148
+ .filter(([name]) => !name.startsWith("@types/"))
149
+ .map(([name, range]) => `${name}@${installedVersion(name) ?? range}`),
150
+ );
151
+
152
+ const hashPackageCode = (hash: Hash, label: string, root: string): void => {
153
+ hashTree(hash, join(label, "src"), join(root, "src"));
154
+ hashTree(hash, join(label, "dist"), join(root, "dist"));
155
+ };
156
+
157
+ const computeCodegenHash = (): string => {
158
+ const hash = createHash("sha256");
159
+ hash.update(dependencyVersions().join(","));
160
+ hashPackageCode(hash, "codegen", PACKAGE_ROOT);
161
+ hashTree(hash, "overrides", OVERRIDES_ROOT);
162
+ const utilsRoot = packageRootFor("@gtkx/utils");
163
+
164
+ if (utilsRoot !== undefined) {
165
+ hashPackageCode(hash, "@gtkx/utils", utilsRoot);
166
+ }
167
+
168
+ return hash.digest("hex");
169
+ };
170
+
171
+ const codegenHash = (): string => {
172
+ const cached = codegenHashCache.get("codegen");
173
+
174
+ if (cached !== undefined) {
175
+ return cached;
176
+ }
177
+
178
+ const value = computeCodegenHash();
179
+ codegenHashCache.set("codegen", value);
180
+
181
+ return value;
182
+ };
183
+
66
184
  const hashGi = (inputs: GiInputs): string => {
67
185
  const hash = createHash("sha256");
68
- hash.update(CODEGEN_VERSION);
186
+ hash.update(codegenHash());
187
+ hash.update("\n");
188
+ hash.update(JSON.stringify(EXTERNAL_NAMESPACES));
69
189
  hash.update("\n");
70
190
  hash.update(sortAlpha(inputs.libraries));
71
191
  hash.update("\n");
@@ -73,9 +193,16 @@ const hashGi = (inputs: GiInputs): string => {
73
193
  hash.update("\n");
74
194
  hash.update(String(inputs.isByteArrayTyped));
75
195
  hash.update("\n");
76
- const hashedFiles = sortStrings([...inputs.girFiles, ...overrideFiles()]);
196
+ hash.update(String(inputs.isValueUnwrapped));
197
+ hash.update("\n");
198
+ hash.update(String(inputs.isFinishTrimmed));
199
+ hash.update("\n");
200
+ hash.update(String(inputs.isInoutInPlace));
201
+ hash.update("\n");
202
+ hash.update(String(inputs.storeVersion));
203
+ const girFiles = sortOrdinal(inputs.girFiles);
77
204
 
78
- for (const file of hashedFiles) {
205
+ for (const file of girFiles) {
79
206
  hash.update("\n");
80
207
  hash.update(file);
81
208
  hash.update("\0");
@@ -161,19 +288,20 @@ const isDocsOutputFresh = (outDir: string, inputs: GiInputs, input: DocsFingerpr
161
288
  };
162
289
 
163
290
  const serializeModuleExports = (map: Record<string, ModuleExport>): [string, string, string][] =>
164
- sortStrings(Object.keys(map)).map((type) => [type, map[type]?.module ?? "", map[type]?.export ?? ""]);
291
+ sortOrdinal(Object.keys(map)).map((type) => [type, map[type]?.module ?? "", map[type]?.export ?? ""]);
165
292
 
166
293
  const serializeOmittedProps = (map: Record<string, string[]>): [string, string][] =>
167
- sortStrings(Object.keys(map)).map((type) => [type, sortAlpha(map[type] ?? [])]);
294
+ sortOrdinal(Object.keys(map)).map((type) => [type, sortAlpha(map[type] ?? [])]);
168
295
 
169
296
  const hashJsx = (input: JsxFingerprintInput): string =>
170
297
  createHash("sha256")
171
298
  .update(
172
299
  JSON.stringify([
173
- CODEGEN_VERSION,
300
+ codegenHash(),
174
301
  input.reactVersion,
302
+ sortOrdinal(input.reactSubexports),
175
303
  serializeModuleExports(input.components),
176
- sortStrings(input.lazyElements),
304
+ sortOrdinal(input.lazyElements),
177
305
  serializeModuleExports(input.props),
178
306
  serializeOmittedProps(input.omittedProps),
179
307
  ]),
package/src/gi.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import type { Library } from "./gir/library.js";
2
2
  import type { StoreOptions } from "./store/store-fs.js";
3
3
  import { computeGiFingerprint } from "./fingerprint.js";
4
+ import { externalPackageFor } from "./gir/external-namespaces.js";
4
5
  import { namespaceDirectory } from "./gir/namespace.js";
5
- import { type GiNamespaceInput, writeGiStore } from "./store/gi-store.js";
6
+ import { type GiExternalNamespaceInput, type GiNamespaceInput, writeGiStore } from "./store/gi-store.js";
6
7
  import { generateNamespaceModule } from "./store/gi/pipeline.js";
7
8
 
8
9
  type GiCodegenOptions = {
@@ -10,25 +11,59 @@ type GiCodegenOptions = {
10
11
  libraries: string[];
11
12
  girPath: string[];
12
13
  isByteArrayTyped: boolean;
14
+ isValueUnwrapped: boolean;
15
+ isFinishTrimmed: boolean;
16
+ isInoutInPlace: boolean;
13
17
  };
14
18
 
15
- const runGiCodegen = (library: Library, options: GiCodegenOptions): number => {
16
- const { gi, libraries, girPath, isByteArrayTyped } = options;
19
+ type SplitNamespaces = {
20
+ namespaces: GiNamespaceInput[];
21
+ externalNamespaces: GiExternalNamespaceInput[];
22
+ };
23
+
24
+ const splitNamespaces = (library: Library): SplitNamespaces => {
25
+ const namespaces: GiNamespaceInput[] = [];
26
+ const externalNamespaces: GiExternalNamespaceInput[] = [];
27
+
28
+ for (const namespace of library.namespaces.values()) {
29
+ const packageName = externalPackageFor(namespace.name);
30
+
31
+ if (packageName !== undefined) {
32
+ externalNamespaces.push({
33
+ directory: namespaceDirectory(namespace),
34
+ packageName,
35
+ girFile: namespace.girFile,
36
+ });
17
37
 
18
- const namespaces: GiNamespaceInput[] = Array.from(library.namespaces.values(), (namespace) => ({
19
- directory: namespaceDirectory(namespace),
20
- rawSource: generateNamespaceModule(namespace, library),
21
- girFile: namespace.girFile,
22
- }));
38
+ continue;
39
+ }
40
+
41
+ namespaces.push({
42
+ directory: namespaceDirectory(namespace),
43
+ rawSource: generateNamespaceModule(namespace, library),
44
+ girFile: namespace.girFile,
45
+ });
46
+ }
47
+
48
+ return { namespaces, externalNamespaces };
49
+ };
50
+
51
+ const runGiCodegen = (library: Library, options: GiCodegenOptions): number => {
52
+ const { gi, libraries, girPath, isByteArrayTyped, isValueUnwrapped, isFinishTrimmed, isInoutInPlace } = options;
53
+ const { namespaces, externalNamespaces } = splitNamespaces(library);
23
54
 
24
55
  const fingerprint = computeGiFingerprint({
25
56
  girFiles: library.girFiles,
26
57
  libraries: [...libraries],
27
58
  girPath: [...girPath],
59
+ storeVersion: gi.version,
28
60
  isByteArrayTyped,
61
+ isValueUnwrapped,
62
+ isFinishTrimmed,
63
+ isInoutInPlace,
29
64
  });
30
65
 
31
- writeGiStore(gi, namespaces, fingerprint);
66
+ writeGiStore(gi, namespaces, externalNamespaces, fingerprint);
32
67
 
33
68
  return library.namespaces.size;
34
69
  };
@@ -0,0 +1,8 @@
1
+ type ExternalNamespace = { namespace: string; packageName: string };
2
+
3
+ const EXTERNAL_NAMESPACES: ExternalNamespace[] = [{ namespace: "cairo", packageName: "@gtkx/cairo" }];
4
+
5
+ const externalPackageFor = (namespaceName: string): string | undefined =>
6
+ EXTERNAL_NAMESPACES.find((entry) => entry.namespace === namespaceName)?.packageName;
7
+
8
+ export { EXTERNAL_NAMESPACES, externalPackageFor, type ExternalNamespace };
@@ -0,0 +1,5 @@
1
+ const FUNCTIONS_MISSING_FINISH_FUNC: Map<string, string> = new Map([
2
+ ["g_file_replace_contents_bytes_async", "replace_contents_finish"],
3
+ ]);
4
+
5
+ export { FUNCTIONS_MISSING_FINISH_FUNC };
@@ -1,9 +1,12 @@
1
1
  import type { GirAnnotations } from "./annotations.js";
2
2
  import type { ParseContext } from "./type-id.js";
3
3
  import { type CursorParameterNames, PARAMETERS_MISSING_ARRAY_EXTENT } from "./cursor-overrides.js";
4
+ import { FUNCTIONS_MISSING_FINISH_FUNC } from "./finish-overrides.js";
5
+ import { HIDDEN_SYMBOLS } from "./hidden-symbols.js";
4
6
  import { PARAMETERS_MISSING_NULLABLE_ANNOTATION } from "./nullable-overrides.js";
5
7
  import { type GirParameter, type GirReturnValue, parameterFromNode, parseCallable } from "./parameter.js";
6
8
  import { attr, getChild, type RawNode } from "./parse.js";
9
+ import { RETURNS_MISSING_UCS4_ARRAY_TYPE } from "./ucs4-overrides.js";
7
10
 
8
11
  /** A callable declared by a GIR namespace, class, or record: a function, a method, or a constructor. */
9
12
  type GirFunction = {
@@ -17,13 +20,16 @@ type GirFunction = {
17
20
  cIdentifier: string | undefined;
18
21
  /** Whether the C function takes a trailing `GError` out parameter and can fail. */
19
22
  throws: boolean;
20
- /** Whether the GIR marks the callable as introspectable, and so bindable. */
23
+ /** Whether the callable is bindable: the GIR marks it introspectable and GTKX does not hide it. */
21
24
  introspectable: boolean;
22
25
  /** Qualified `Type.member` the GIR redirects the callable to, from its `moved-to` annotation. */
23
26
  movedTo: string | undefined;
24
27
  /** Name of the callable that shadows this one, from the GIR's `shadowed-by` annotation. */
25
28
  shadowedBy: string | undefined;
26
- /** Name of the callable that completes this asynchronous one, from the GIR's `glib:finish-func`. */
29
+ /**
30
+ * Name of the callable that completes this asynchronous one, from the GIR's `glib:finish-func`
31
+ * or a GTKX override for a GIR that lacks the annotation.
32
+ */
27
33
  finishFunc: string | undefined;
28
34
  /** The instance the callable is invoked on, absent for constructors and namespace-level functions. */
29
35
  instance: GirParameter | undefined;
@@ -84,20 +90,52 @@ const bindMissingArrayExtent = (fn: GirFunction): GirFunction => {
84
90
  return fn;
85
91
  };
86
92
 
93
+ const bindMissingUcs4ReturnArray = (fn: GirFunction, context: ParseContext): GirFunction => {
94
+ if (fn.cIdentifier !== undefined && RETURNS_MISSING_UCS4_ARRAY_TYPE.has(fn.cIdentifier)) {
95
+ fn.returnValue.type = context.addContainer({
96
+ kind: "carray",
97
+ element: context.addPrimitive("unichar"),
98
+ elementCType: "gunichar",
99
+ arrayCType: "gunichar*",
100
+ lengthParameterIndex: undefined,
101
+ fixedSize: undefined,
102
+ isZeroTerminated: true,
103
+ });
104
+ }
105
+
106
+ return fn;
107
+ };
108
+
109
+ const annotatedFinishFunc = (node: RawNode, cIdentifier: string | undefined): string | undefined => {
110
+ const annotated = attr(node, "glib:finish-func");
111
+
112
+ if (annotated !== undefined) {
113
+ return annotated;
114
+ }
115
+
116
+ return cIdentifier === undefined ? undefined : FUNCTIONS_MISSING_FINISH_FUNC.get(cIdentifier);
117
+ };
118
+
119
+ const isHiddenSymbol = (cIdentifier: string | undefined): boolean =>
120
+ cIdentifier !== undefined && HIDDEN_SYMBOLS.has(cIdentifier);
121
+
87
122
  const functionFromNode = (node: RawNode, context: ParseContext): GirFunction => {
88
123
  const instanceNode = getChild(getChild(node, "parameters"), "instance-parameter");
89
-
90
- return bindMissingArrayExtent(
91
- relaxMissingNullable({
92
- ...parseCallable(node, context),
93
- name: attr(node, "shadows") ?? attr(node, "name") ?? "",
94
- cIdentifier: attr(node, "c:identifier"),
95
- movedTo: attr(node, "moved-to"),
96
- shadowedBy: attr(node, "shadowed-by"),
97
- finishFunc: attr(node, "glib:finish-func"),
98
- instance: instanceNode === undefined ? undefined : parameterFromNode(instanceNode, context),
99
- }),
100
- );
124
+ const callable = parseCallable(node, context);
125
+ const cIdentifier = attr(node, "c:identifier");
126
+
127
+ const fn: GirFunction = {
128
+ ...callable,
129
+ introspectable: callable.introspectable && !isHiddenSymbol(cIdentifier),
130
+ name: attr(node, "shadows") ?? attr(node, "name") ?? "",
131
+ cIdentifier,
132
+ movedTo: attr(node, "moved-to"),
133
+ shadowedBy: attr(node, "shadowed-by"),
134
+ finishFunc: annotatedFinishFunc(node, cIdentifier),
135
+ instance: instanceNode === undefined ? undefined : parameterFromNode(instanceNode, context),
136
+ };
137
+
138
+ return bindMissingUcs4ReturnArray(bindMissingArrayExtent(relaxMissingNullable(fn)), context);
101
139
  };
102
140
 
103
141
  export { functionFromNode, type GirFunction };
@@ -0,0 +1,39 @@
1
+ const HIDDEN_SYMBOLS: Set<string> = new Set([
2
+ "g_action_map_add_action_entries",
3
+ "g_ascii_dtostr",
4
+ "g_ascii_formatd",
5
+ "g_file_info_set_attribute",
6
+ "g_file_set_attribute",
7
+ "g_log_set_writer_func",
8
+ "g_object_get_data",
9
+ "g_object_get_qdata",
10
+ "g_object_set_data",
11
+ "g_object_set_data_full",
12
+ "g_object_set_qdata",
13
+ "g_object_set_qdata_full",
14
+ "g_object_steal_data",
15
+ "g_object_steal_qdata",
16
+ "g_param_spec_get_qdata",
17
+ "g_param_spec_set_qdata",
18
+ "g_param_spec_set_qdata_full",
19
+ "g_param_spec_steal_qdata",
20
+ "g_ref_string_acquire",
21
+ "g_ref_string_length",
22
+ "g_ref_string_new",
23
+ "g_ref_string_new_intern",
24
+ "g_ref_string_new_len",
25
+ "g_ref_string_release",
26
+ "g_stpcpy",
27
+ "g_strcanon",
28
+ "g_strchomp",
29
+ "g_strchug",
30
+ "g_strdelimit",
31
+ "g_strdown",
32
+ "g_strreverse",
33
+ "g_strup",
34
+ "g_thread_exit",
35
+ "g_thread_new",
36
+ "g_thread_try_new",
37
+ ]);
38
+
39
+ export { HIDDEN_SYMBOLS };
@@ -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/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) {