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

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 (113) hide show
  1. package/dist/analysis/descriptor-render.d.ts +2 -0
  2. package/dist/analysis/descriptor-render.d.ts.map +1 -1
  3. package/dist/analysis/descriptor-render.js +25 -4
  4. package/dist/analysis/descriptor-render.js.map +1 -1
  5. package/dist/analysis/ts-type.d.ts.map +1 -1
  6. package/dist/analysis/ts-type.js +4 -8
  7. package/dist/analysis/ts-type.js.map +1 -1
  8. package/dist/docs/api-reference.d.ts.map +1 -1
  9. package/dist/docs/api-reference.js +4 -8
  10. package/dist/docs/api-reference.js.map +1 -1
  11. package/dist/docs/element-page.d.ts.map +1 -1
  12. package/dist/docs/element-page.js +5 -4
  13. package/dist/docs/element-page.js.map +1 -1
  14. package/dist/docs/pipeline.d.ts +2 -0
  15. package/dist/docs/pipeline.d.ts.map +1 -1
  16. package/dist/docs/pipeline.js +2 -0
  17. package/dist/docs/pipeline.js.map +1 -1
  18. package/dist/fingerprint.d.ts +1 -0
  19. package/dist/fingerprint.d.ts.map +1 -1
  20. package/dist/fingerprint.js +3 -0
  21. package/dist/fingerprint.js.map +1 -1
  22. package/dist/gi.d.ts +7 -2
  23. package/dist/gi.d.ts.map +1 -1
  24. package/dist/gi.js +4 -2
  25. package/dist/gi.js.map +1 -1
  26. package/dist/gir/libraries.d.ts +4 -3
  27. package/dist/gir/libraries.d.ts.map +1 -1
  28. package/dist/gir/libraries.js +2 -1
  29. package/dist/gir/libraries.js.map +1 -1
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/jsx.d.ts +2 -0
  35. package/dist/jsx.d.ts.map +1 -1
  36. package/dist/jsx.js +4 -0
  37. package/dist/jsx.js.map +1 -1
  38. package/dist/react/element-config.d.ts +3 -0
  39. package/dist/react/element-config.d.ts.map +1 -1
  40. package/dist/react/element-config.js +4 -1
  41. package/dist/react/element-config.js.map +1 -1
  42. package/dist/runner.d.ts +2 -0
  43. package/dist/runner.d.ts.map +1 -1
  44. package/dist/runner.js +4 -1
  45. package/dist/runner.js.map +1 -1
  46. package/dist/store/gi/callables.d.ts.map +1 -1
  47. package/dist/store/gi/callables.js +0 -5
  48. package/dist/store/gi/callables.js.map +1 -1
  49. package/dist/store/gi/function.d.ts.map +1 -1
  50. package/dist/store/gi/function.js +0 -2
  51. package/dist/store/gi/function.js.map +1 -1
  52. package/dist/store/gi/method.d.ts.map +1 -1
  53. package/dist/store/gi/method.js +1 -0
  54. package/dist/store/gi/method.js.map +1 -1
  55. package/dist/store/gi/record-constructor.d.ts.map +1 -1
  56. package/dist/store/gi/record-constructor.js +5 -4
  57. package/dist/store/gi/record-constructor.js.map +1 -1
  58. package/dist/store/gi/record-field-accessor.d.ts +2 -1
  59. package/dist/store/gi/record-field-accessor.d.ts.map +1 -1
  60. package/dist/store/gi/record-field-accessor.js +22 -6
  61. package/dist/store/gi/record-field-accessor.js.map +1 -1
  62. package/dist/store/gi/record.d.ts.map +1 -1
  63. package/dist/store/gi/record.js +0 -7
  64. package/dist/store/gi/record.js.map +1 -1
  65. package/dist/store/gi/value-marshalable.d.ts +15 -0
  66. package/dist/store/gi/value-marshalable.d.ts.map +1 -0
  67. package/dist/store/gi/value-marshalable.js +66 -0
  68. package/dist/store/gi/value-marshalable.js.map +1 -0
  69. package/dist/store/jsx/intrinsic-elements.d.ts +1 -2
  70. package/dist/store/jsx/intrinsic-elements.d.ts.map +1 -1
  71. package/dist/store/jsx/intrinsic-elements.js +1 -2
  72. package/dist/store/jsx/intrinsic-elements.js.map +1 -1
  73. package/dist/store/jsx/omitted-props.d.ts +10 -0
  74. package/dist/store/jsx/omitted-props.d.ts.map +1 -0
  75. package/dist/store/jsx/omitted-props.js +22 -0
  76. package/dist/store/jsx/omitted-props.js.map +1 -0
  77. package/dist/store/jsx/pipeline.d.ts +2 -0
  78. package/dist/store/jsx/pipeline.d.ts.map +1 -1
  79. package/dist/store/jsx/pipeline.js +2 -0
  80. package/dist/store/jsx/pipeline.js.map +1 -1
  81. package/dist/store/jsx/props.d.ts +1 -1
  82. package/dist/store/jsx/props.d.ts.map +1 -1
  83. package/dist/store/jsx/props.js +6 -11
  84. package/dist/store/jsx/props.js.map +1 -1
  85. package/package.json +3 -4
  86. package/src/analysis/descriptor-render.ts +35 -3
  87. package/src/analysis/ts-type.ts +4 -11
  88. package/src/docs/api-reference.ts +4 -9
  89. package/src/docs/element-page.ts +5 -9
  90. package/src/docs/pipeline.ts +3 -0
  91. package/src/fingerprint.ts +16 -2
  92. package/src/gi.ts +12 -3
  93. package/src/gir/libraries.ts +5 -4
  94. package/src/index.ts +1 -0
  95. package/src/jsx.ts +5 -0
  96. package/src/react/element-config.ts +8 -2
  97. package/src/runner.ts +8 -1
  98. package/src/store/gi/callables.ts +0 -6
  99. package/src/store/gi/function.ts +0 -2
  100. package/src/store/gi/method.ts +1 -0
  101. package/src/store/gi/record-constructor.ts +9 -4
  102. package/src/store/gi/record-field-accessor.ts +41 -6
  103. package/src/store/gi/record.ts +0 -9
  104. package/src/store/gi/value-marshalable.ts +96 -0
  105. package/src/store/jsx/intrinsic-elements.ts +0 -9
  106. package/src/store/jsx/omitted-props.ts +32 -0
  107. package/src/store/jsx/pipeline.ts +3 -0
  108. package/src/store/jsx/props.ts +6 -18
  109. package/dist/store/gi/class-struct-record.d.ts +0 -10
  110. package/dist/store/gi/class-struct-record.d.ts.map +0 -1
  111. package/dist/store/gi/class-struct-record.js +0 -52
  112. package/dist/store/gi/class-struct-record.js.map +0 -1
  113. package/src/store/gi/class-struct-record.ts +0 -72
@@ -9,7 +9,6 @@ import {
9
9
  type ListType,
10
10
  type TypeId,
11
11
  } from "../gir/type-id.js";
12
- import { isClassStructRecord } from "../store/gi/class-struct-record.js";
13
12
  import { gtypeTsType } from "../store/gi/gtype-binding.js";
14
13
 
15
14
  type ReferenceName = {
@@ -25,18 +24,13 @@ type TsTypeTarget = {
25
24
  renderGtype: () => string;
26
25
  };
27
26
 
28
- const willEmitEntity = (library: Library, type: EntityType): boolean => {
27
+ const willEmitEntity = (type: EntityType): boolean => {
29
28
  switch (type.kind) {
30
29
  case "callback": {
31
30
  return type.value.introspectable && type.value.name.length > 0;
32
31
  }
33
32
  case "record": {
34
- return (
35
- type.value.introspectable &&
36
- !type.value.isVtable &&
37
- type.value.name.length > 0 &&
38
- !isClassStructRecord(library, type.namespace.name, type.value)
39
- );
33
+ return type.value.introspectable && type.value.name.length > 0;
40
34
  }
41
35
  case "alias":
42
36
  case "class":
@@ -51,11 +45,10 @@ const renderPrimitiveType = (target: TsTypeTarget, type: Extract<GirType, { kind
51
45
  type.category === "gtype" ? target.renderGtype() : PRIMITIVE_TS_TYPE[type.category];
52
46
 
53
47
  const renderEntityType = (
54
- library: Library,
55
48
  target: TsTypeTarget,
56
49
  type: EntityType,
57
50
  name: ReferenceName | undefined,
58
- ): string => renderNamedType(target, type, willEmitEntity(library, type) ? name : undefined);
51
+ ): string => renderNamedType(target, type, willEmitEntity(type) ? name : undefined);
59
52
 
60
53
  const renderBaseType = (library: Library, target: TsTypeTarget, ref: TypeId | undefined): string => {
61
54
  if (ref === undefined) {
@@ -82,7 +75,7 @@ const renderBaseType = (library: Library, target: TsTypeTarget, ref: TypeId | un
82
75
  case "record":
83
76
  case "enum":
84
77
  case "alias": {
85
- return renderEntityType(library, target, type, name);
78
+ return renderEntityType(target, type, name);
86
79
  }
87
80
  case "carray":
88
81
  case "list":
@@ -5,7 +5,6 @@ import type { GirRecord } from "../gir/record.js";
5
5
  import { Library } from "../gir/library.js";
6
6
  import { type GirNamespace, namespaceDirectory } from "../gir/namespace.js";
7
7
  import { dedupeCallables, isEmittableCallable } from "../store/gi/callables.js";
8
- import { isClassStructRecord } from "../store/gi/class-struct-record.js";
9
8
  import { namespaceFunctionExportName } from "../store/gi/function.js";
10
9
  import { collectIntrinsicElementClasses, type GlibNamedClass } from "../store/jsx/intrinsic-elements.js";
11
10
  import { createElementPageContext, type ElementPageContext, renderElementPage } from "./element-page.js";
@@ -136,23 +135,19 @@ const classEntries = (namespace: GirNamespace): GiSymbolEntry[] => {
136
135
  return entries;
137
136
  };
138
137
 
139
- const recordEntry = (library: Library, namespace: GirNamespace, record: GirRecord): GiSymbolEntry | undefined => {
138
+ const recordEntry = (namespace: GirNamespace, record: GirRecord): GiSymbolEntry | undefined => {
140
139
  if (!record.introspectable || record.isVtable || record.name.length === 0) {
141
140
  return undefined;
142
141
  }
143
142
 
144
- if (isClassStructRecord(library, namespace.name, record)) {
145
- return undefined;
146
- }
147
-
148
143
  return { kind: "record", namespace, name: record.name, doc: record.doc, record };
149
144
  };
150
145
 
151
- const recordEntries = (library: Library, namespace: GirNamespace): GiSymbolEntry[] => {
146
+ const recordEntries = (namespace: GirNamespace): GiSymbolEntry[] => {
152
147
  const entries: GiSymbolEntry[] = [];
153
148
 
154
149
  for (const record of namespace.records) {
155
- const entry = recordEntry(library, namespace, record);
150
+ const entry = recordEntry(namespace, record);
156
151
 
157
152
  if (entry !== undefined) {
158
153
  entries.push(entry);
@@ -322,7 +317,7 @@ class ApiReference {
322
317
  private indexNamespace(namespace: GirNamespace): void {
323
318
  const entries = [
324
319
  ...classEntries(namespace),
325
- ...recordEntries(this.library, namespace),
320
+ ...recordEntries(namespace),
326
321
  ...valueEntries(namespace),
327
322
  ];
328
323
 
@@ -13,6 +13,7 @@ import {
13
13
  newlyImplementedInterfaces,
14
14
  signalHandlerName,
15
15
  } from "../store/jsx/intrinsic-elements.js";
16
+ import { isOmittedProp } from "../store/jsx/omitted-props.js";
16
17
  import { isObjectProp } from "../store/jsx/props.js";
17
18
  import {
18
19
  classMethodEntries,
@@ -120,12 +121,7 @@ const propertyEntry = (
120
121
  property: GirProperty,
121
122
  jsName: string,
122
123
  ): PropEntry => {
123
- const isObject = isObjectProp(
124
- { library: context.library, klass: owner.klass, namespace: owner.namespace },
125
- property,
126
- jsName,
127
- );
128
-
124
+ const isObject = isObjectProp(context.library, property);
129
125
  const baseType = renderDocsType(context.library, property.type, false);
130
126
  const type = isObject ? `${baseType} | ReactElement` : baseType;
131
127
  const meta: string[] = [`\`${type}\``];
@@ -149,14 +145,14 @@ const propertyEntry = (
149
145
  return { name: jsName, meta: meta.join(" · "), doc: docMarkdown(property.doc) };
150
146
  };
151
147
 
152
- const propJsName = (property: GirProperty, seen: Set<string>): string | undefined => {
148
+ const propJsName = (property: GirProperty, owner: MemberOwner, seen: Set<string>): string | undefined => {
153
149
  if (!property.introspectable) {
154
150
  return undefined;
155
151
  }
156
152
 
157
153
  const jsName = toCamelIdentifier(property.name);
158
154
 
159
- if (seen.has(jsName)) {
155
+ if (seen.has(jsName) || isOmittedProp(owner.glibName, jsName)) {
160
156
  return undefined;
161
157
  }
162
158
 
@@ -169,7 +165,7 @@ const ownerPropEntries = (context: ElementPageContext, owner: MemberOwner, seen:
169
165
  const entries: PropEntry[] = [];
170
166
 
171
167
  for (const property of owner.klass.properties) {
172
- const jsName = propJsName(property, seen);
168
+ const jsName = propJsName(property, owner, seen);
173
169
 
174
170
  if (jsName !== undefined) {
175
171
  entries.push(propertyEntry(context, owner, property, jsName));
@@ -6,6 +6,7 @@ import { Library } from "../gir/library.js";
6
6
  import { namespaceDirectory } from "../gir/namespace.js";
7
7
  import { type ElementProps, setElementProps } from "../store/jsx/element-prop-imports.js";
8
8
  import { collectIntrinsicElementClasses, type GlibNamedClass } from "../store/jsx/intrinsic-elements.js";
9
+ import { type OmittedProps, setOmittedProps } from "../store/jsx/omitted-props.js";
9
10
  import { createElementPageContext, type ElementPageContext, renderElementPage } from "./element-page.js";
10
11
  import { elementSlug, firstSentence, namespaceOrder } from "./render.js";
11
12
 
@@ -27,6 +28,7 @@ type DocsOptions = {
27
28
  outDir: string;
28
29
  basePath?: string;
29
30
  props?: ElementProps;
31
+ omitProps?: OmittedProps;
30
32
  force?: boolean;
31
33
  };
32
34
 
@@ -214,6 +216,7 @@ const writePages = (outDir: string, pages: Page[]): void => {
214
216
 
215
217
  const writeDocs = (options: DocsOptions): DocsResult => {
216
218
  setElementProps(options.props ?? {});
219
+ setOmittedProps(options.omitProps ?? {});
217
220
  const basePath = options.basePath ?? "/reference";
218
221
  const manifestPath = join(options.outDir, MANIFEST_FILENAME);
219
222
  const cached = cachedDocsResult(options, manifestPath);
@@ -21,6 +21,7 @@ type JsxFingerprintInput = {
21
21
  components: Record<string, ModuleExport>;
22
22
  lazyElements: string[];
23
23
  props: Record<string, ModuleExport>;
24
+ omitProps: Record<string, string[]>;
24
25
  };
25
26
 
26
27
  /** The freshness sentinel of the `@gtkx/jsx` store, derived from the React element config. */
@@ -55,6 +56,7 @@ const hashGi = (girFiles: string[], libraries: string[], girPath: string[]): str
55
56
  hash.update(sortAlpha(libraries));
56
57
  hash.update("\n");
57
58
  hash.update(sortAlpha(girPath));
59
+ hash.update("\n");
58
60
  const hashedFiles = sortStrings([...girFiles, ...overrideFiles()]);
59
61
 
60
62
  for (const file of hashedFiles) {
@@ -67,14 +69,22 @@ const hashGi = (girFiles: string[], libraries: string[], girPath: string[]): str
67
69
  return hash.digest("hex");
68
70
  };
69
71
 
70
- const computeGiFingerprint = (girFiles: string[], libraries: string[], girPath: string[]): GiFingerprint => ({
72
+ const computeGiFingerprint = (
73
+ girFiles: string[],
74
+ libraries: string[],
75
+ girPath: string[],
76
+ ): GiFingerprint => ({
71
77
  value: hashGi(girFiles, libraries, girPath),
72
78
  girFiles,
73
79
  libraries,
74
80
  girPath,
75
81
  });
76
82
 
77
- const isGiStoreFresh = (giStoreDir: string, libraries: string[], girPath: string[]): boolean => {
83
+ const isGiStoreFresh = (
84
+ giStoreDir: string,
85
+ libraries: string[],
86
+ girPath: string[],
87
+ ): boolean => {
78
88
  const sentinelPath = join(giStoreDir, FINGERPRINT_FILENAME);
79
89
 
80
90
  if (!existsSync(sentinelPath)) {
@@ -106,6 +116,9 @@ const hasMatchingRecordedInputs = (sentinel: GiFingerprint, libraries: string[],
106
116
  const serializeModuleExports = (map: Record<string, ModuleExport>): [string, string, string][] =>
107
117
  sortStrings(Object.keys(map)).map((type) => [type, map[type]?.module ?? "", map[type]?.export ?? ""]);
108
118
 
119
+ const serializeOmitProps = (map: Record<string, string[]>): [string, string][] =>
120
+ sortStrings(Object.keys(map)).map((type) => [type, sortAlpha(map[type] ?? [])]);
121
+
109
122
  const hashJsx = (input: JsxFingerprintInput): string =>
110
123
  createHash("sha256")
111
124
  .update(
@@ -115,6 +128,7 @@ const hashJsx = (input: JsxFingerprintInput): string =>
115
128
  serializeModuleExports(input.components),
116
129
  sortStrings(input.lazyElements),
117
130
  serializeModuleExports(input.props),
131
+ serializeOmitProps(input.omitProps),
118
132
  ]),
119
133
  )
120
134
  .digest("hex");
package/src/gi.ts CHANGED
@@ -4,23 +4,32 @@ import { namespaceDirectory } from "./gir/namespace.js";
4
4
  import { type GiNamespaceInput, type GiStoreOptions, writeGiStore } from "./store/gi-store.js";
5
5
  import { generateNamespaceModule } from "./store/gi/pipeline.js";
6
6
 
7
+ type GiCodegenOptions = {
8
+ gi: GiStoreOptions;
9
+ libraries: string[];
10
+ girPath: string[];
11
+ };
12
+
7
13
  /**
8
14
  * Generates the `@gtkx/gi` store from the loaded GIR library and links it into the project. This is the
9
15
  * react-free half of codegen: it can run before `@gtkx/gi` (or `@gtkx/react`) exists, since it produces them.
10
16
  *
11
17
  * @returns The number of namespaces written.
12
18
  */
13
- const runGiCodegen = (library: Library, gi: GiStoreOptions, libraries: string[], girPath: string[]): number => {
19
+ const runGiCodegen = (library: Library, options: GiCodegenOptions): number => {
20
+ const { gi, libraries, girPath } = options;
21
+
14
22
  const namespaces: GiNamespaceInput[] = Array.from(library.namespaces.values(), (namespace) => ({
15
23
  directory: namespaceDirectory(namespace),
16
24
  rawSource: generateNamespaceModule(namespace, library),
17
25
  }));
18
26
 
19
- writeGiStore(gi, namespaces, computeGiFingerprint(library.girFiles, [...libraries], [...girPath]));
27
+ const fingerprint = computeGiFingerprint(library.girFiles, [...libraries], [...girPath]);
28
+ writeGiStore(gi, namespaces, fingerprint);
20
29
 
21
30
  return library.namespaces.size;
22
31
  };
23
32
 
24
33
  export { isGiStoreFresh } from "./fingerprint.js";
25
34
  export type { GiStoreOptions } from "./store/gi-store.js";
26
- export { runGiCodegen };
35
+ export { runGiCodegen, type GiCodegenOptions };
@@ -1,14 +1,15 @@
1
- import type { Config } from "@gtkx/config";
2
- import { GIR_LIBRARY_PATTERN, LIBRARIES_WILDCARD } from "@gtkx/config/internal";
3
1
  import { sortStrings } from "@gtkx/utils";
4
2
  import { readdirSync } from "node:fs";
5
3
 
6
4
  type GirNamespace = { name: string; version: string; identifier: string };
5
+ type LibrarySelection = typeof LIBRARIES_WILDCARD | string[] | undefined;
7
6
 
7
+ const LIBRARIES_WILDCARD = "*";
8
+ const GIR_LIBRARY_PATTERN = /^[A-Za-z][A-Za-z0-9]*-\d+(?:\.\d+)*$/;
8
9
  const DEFAULT_LIBRARIES: string[] = ["Gtk-4.0"];
9
10
  const GIR_FILE_SUFFIX = ".gir";
10
11
 
11
- const resolveLibraries = (libraries: Config["libraries"], girPath: string[]): string[] => {
12
+ const resolveLibraries = (libraries: LibrarySelection, girPath: string[]): string[] => {
12
13
  if (libraries === undefined) {
13
14
  return [...DEFAULT_LIBRARIES];
14
15
  }
@@ -99,4 +100,4 @@ const compareVersions = (a: string, b: string): number => {
99
100
  return 0;
100
101
  };
101
102
 
102
- export { resolveLibraries, discoverGirNamespaces };
103
+ export { resolveLibraries, discoverGirNamespaces, type LibrarySelection };
package/src/index.ts CHANGED
@@ -26,3 +26,4 @@ export {
26
26
  } from "./react/element-config.js";
27
27
  export { runCodegen } from "./runner.js";
28
28
  export type { ElementProps } from "./store/jsx/element-prop-imports.js";
29
+ export { mergeOmitProps, type OmittedProps } from "./store/jsx/omitted-props.js";
package/src/jsx.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  } from "./fingerprint.js";
8
8
  import { type ModuleExport, readBuiltinElements } from "./react/element-config.js";
9
9
  import { type JsxStoreOptions, writeJsxStore } from "./store/jsx-store.js";
10
+ import { mergeOmitProps, type OmittedProps } from "./store/jsx/omitted-props.js";
10
11
  import { generateJsxFiles } from "./store/jsx/pipeline.js";
11
12
 
12
13
  type RunJsxCodegenOptions = {
@@ -17,6 +18,7 @@ type RunJsxCodegenOptions = {
17
18
  userComponents: Record<string, ModuleExport>;
18
19
  userLazyElements: string[];
19
20
  userProps: Record<string, ModuleExport>;
21
+ userOmitProps: OmittedProps;
20
22
  giRegenerated: boolean;
21
23
  force: boolean;
22
24
  };
@@ -36,12 +38,14 @@ const runJsxCodegen = async (options: RunJsxCodegenOptions): Promise<RunJsxCodeg
36
38
  const components = { ...builtin.components, ...options.userComponents };
37
39
  const lazyElements = [...builtin.lazyElements, ...options.userLazyElements];
38
40
  const props = { ...builtin.props, ...options.userProps };
41
+ const omitProps = mergeOmitProps(builtin.omitProps, options.userOmitProps);
39
42
 
40
43
  const fingerprintInput: JsxFingerprintInput = {
41
44
  reactVersion: options.jsx.version,
42
45
  components,
43
46
  lazyElements,
44
47
  props,
48
+ omitProps,
45
49
  };
46
50
 
47
51
  if (!options.force && !options.giRegenerated) {
@@ -57,6 +61,7 @@ const runJsxCodegen = async (options: RunJsxCodegenOptions): Promise<RunJsxCodeg
57
61
  components,
58
62
  lazyElements,
59
63
  props,
64
+ omitProps,
60
65
  });
61
66
 
62
67
  writeJsxStore(options.jsx, namespaces, metadata, {
@@ -1,14 +1,16 @@
1
1
  import type { ModuleExport } from "@gtkx/react/config";
2
2
  import { readdirSync } from "node:fs";
3
+ import type { OmittedProps } from "../store/jsx/omitted-props.js";
3
4
 
4
5
  /** The codegen-relevant subset of a React element config; behaviors are ignored at codegen time. */
5
- type BuiltinElement = { component?: ModuleExport; lazy?: boolean; props?: ModuleExport };
6
+ type BuiltinElement = { component?: ModuleExport; lazy?: boolean; props?: ModuleExport; omitProps?: string[] };
6
7
 
7
8
  /** The framework's built-in element config, split into the maps codegen consumes. */
8
9
  type BuiltinElements = {
9
10
  components: Record<string, ModuleExport>;
10
11
  lazyElements: string[];
11
12
  props: Record<string, ModuleExport>;
13
+ omitProps: OmittedProps;
12
14
  };
13
15
 
14
16
  const CONFIG_ENTRYPOINT = "config";
@@ -59,6 +61,10 @@ const applyBuiltinElement = (target: BuiltinElements, type: string, config: Buil
59
61
  target.props[type] = config.props;
60
62
  }
61
63
 
64
+ if (config.omitProps !== undefined) {
65
+ target.omitProps[type] = [...(target.omitProps[type] ?? []), ...config.omitProps];
66
+ }
67
+
62
68
  if (config.lazy === true) {
63
69
  target.lazyElements.push(type);
64
70
  }
@@ -78,7 +84,7 @@ const collectBuiltinElements = (target: BuiltinElements, elements: Record<string
78
84
  const readBuiltinElements = async (reactSubexports: string[], giStoreDir: string): Promise<BuiltinElements> => {
79
85
  const present = presentNamespaceDirs(giStoreDir);
80
86
  const entrypoints = configEntrypoints(reactSubexports, present);
81
- const result: BuiltinElements = { components: {}, lazyElements: [], props: {} };
87
+ const result: BuiltinElements = { components: {}, lazyElements: [], props: {}, omitProps: {} };
82
88
 
83
89
  for (const entrypoint of entrypoints) {
84
90
  collectBuiltinElements(result, await importBuiltinElements(entrypoint));
package/src/runner.ts CHANGED
@@ -2,6 +2,7 @@ import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import type { GiStoreOptions } from "./store/gi-store.js";
4
4
  import type { JsxStoreOptions } from "./store/jsx-store.js";
5
+ import type { OmittedProps } from "./store/jsx/omitted-props.js";
5
6
  import { checkModules } from "./compile.js";
6
7
  import { isGiStoreFresh } from "./fingerprint.js";
7
8
  import { runGiCodegen } from "./gi.js";
@@ -26,6 +27,7 @@ type CodegenRunnerOptions = {
26
27
  userComponents?: Record<string, ModuleExport>;
27
28
  userLazyElements?: string[];
28
29
  userProps?: Record<string, ModuleExport>;
30
+ userOmitProps?: OmittedProps;
29
31
  gl?: GlCodegenOptions;
30
32
  force?: boolean;
31
33
  };
@@ -95,11 +97,13 @@ const jsxUserOptions = (
95
97
  userComponents: Record<string, ModuleExport>;
96
98
  userLazyElements: string[];
97
99
  userProps: Record<string, ModuleExport>;
100
+ userOmitProps: OmittedProps;
98
101
  } => ({
99
102
  reactSubexports: options.reactSubexports ?? [],
100
103
  userComponents: options.userComponents ?? {},
101
104
  userLazyElements: options.userLazyElements ?? [],
102
105
  userProps: options.userProps ?? {},
106
+ userOmitProps: options.userOmitProps ?? {},
103
107
  });
104
108
 
105
109
  const emitJsxStore = async (input: {
@@ -140,7 +144,10 @@ const emitStoresWithConfig = async (config: {
140
144
  let library: Library | undefined;
141
145
  const loadLibrary = (): Library => (library ??= Library.load(libraries, girPath));
142
146
  const isGiRegenerated = options.force === true || !isGiStoreFresh(gi.storeDir, libraries, girPath);
143
- const namespaces = isGiRegenerated ? runGiCodegen(loadLibrary(), gi, libraries, girPath) : 0;
147
+
148
+ const namespaces = isGiRegenerated
149
+ ? runGiCodegen(loadLibrary(), { gi, libraries, girPath })
150
+ : 0;
144
151
 
145
152
  if (jsx === undefined) {
146
153
  return { regenerated: isGiRegenerated, namespaces, intrinsicElements: 0 };
@@ -5,7 +5,6 @@ import { hasCallerAllocatedArrayLength } from "../../analysis/param-structure.js
5
5
  import { renderJsDoc } from "../../writer/doc.js";
6
6
  import { renderBlock } from "../../writer/emit.js";
7
7
  import { matchAsyncFinish } from "./async.js";
8
- import { hasClassStructReference } from "./class-struct-record.js";
9
8
  import { renderFnExpression } from "./function.js";
10
9
  import { gtypeMemberDeclaration } from "./gtype-binding.js";
11
10
  import {
@@ -111,10 +110,6 @@ const renderBinding = (
111
110
  return undefined;
112
111
  }
113
112
 
114
- if (hasClassStructReference(context, callable)) {
115
- return undefined;
116
- }
117
-
118
113
  const expression = renderFnExpression(context, callable);
119
114
 
120
115
  if (expression === undefined) {
@@ -371,7 +366,6 @@ const isEmittableCallable = (context: ModuleContext, callable: GirFunction): boo
371
366
  callable.introspectable &&
372
367
  callable.shadowedBy === undefined &&
373
368
  callable.cIdentifier !== undefined &&
374
- !hasClassStructReference(context, callable) &&
375
369
  !hasCallerAllocatedArrayLength(context.library, callable);
376
370
 
377
371
  const constructorMemberName = (girName: string): string | undefined => {
@@ -7,7 +7,6 @@ import { hasCallerAllocatedArrayLength } from "../../analysis/param-structure.js
7
7
  import { renderJsDoc } from "../../writer/doc.js";
8
8
  import { arrayLiteral, renderBlock } from "../../writer/emit.js";
9
9
  import { matchAsyncFinish } from "./async.js";
10
- import { hasClassStructReference } from "./class-struct-record.js";
11
10
  import {
12
11
  planCallArgs,
13
12
  renderMethodBody,
@@ -63,7 +62,6 @@ const canEmitNamespaceFunction = (context: ModuleContext, fn: GirFunction): bool
63
62
  !isMovedOntoEmittedMember(context, fn) &&
64
63
  fn.shadowedBy === undefined &&
65
64
  fn.cIdentifier !== undefined &&
66
- !hasClassStructReference(context, fn) &&
67
65
  !hasCallerAllocatedArrayLength(context.library, fn);
68
66
 
69
67
  const generateNamespaceFunction = (context: ModuleContext, fn: GirFunction): void => {
@@ -315,6 +315,7 @@ const renderReturnDescriptor = (context: ModuleContext, fn: GirFunction): string
315
315
  return renderDescriptor(context, fn.returnValue.type, fn.returnValue.transferOwnership, {
316
316
  argIndexOffset: instanceOffset,
317
317
  argIndexMap: emittedArgIndices(fn, instanceOffset),
318
+ isNewlyCreated: fn.instance === undefined,
318
319
  });
319
320
  };
320
321
 
@@ -7,8 +7,9 @@ import { renderDescriptor } from "../../analysis/descriptor-render.js";
7
7
  import { renderTsType } from "../../analysis/ts-type.js";
8
8
  import { renderBlock, renderBracedOrEmpty } from "../../writer/emit.js";
9
9
  import { renderSourceGtype } from "./gtype-binding.js";
10
- import { emitFieldWrite, isEmittableField } from "./record-field-accessor.js";
10
+ import { emitFieldWrite, isEmittableField, isInlineField } from "./record-field-accessor.js";
11
11
  import { computeRecordFieldSlots, type RecordFieldSlot } from "./record-layout.js";
12
+ import { isConstructibleRecord } from "./value-marshalable.js";
12
13
 
13
14
  type WritableFieldSlot = RecordFieldSlot & { field: GirField & { type: TypeId } };
14
15
 
@@ -24,7 +25,7 @@ const renderRecordConstructorPropsInterface = (
24
25
  ): string => {
25
26
  const head = `export interface ${className}ConstructorProps`;
26
27
 
27
- if (isOpaque(record)) {
28
+ if (isOpaque(record) || !isConstructibleRecord(context, context.namespace.name, record)) {
28
29
  return renderBracedOrEmpty(head, "");
29
30
  }
30
31
 
@@ -68,7 +69,7 @@ const renderRecordConstructor = (
68
69
  ): string => {
69
70
  const superCall = isErrorSubclass ? ["super();"] : [];
70
71
 
71
- if (isOpaque(record)) {
72
+ if (isOpaque(record) || !isConstructibleRecord(context, context.namespace.name, record)) {
72
73
  return renderOpaqueConstructor(className, superCall);
73
74
  }
74
75
 
@@ -103,7 +104,11 @@ const allocArgs = (context: ModuleContext, record: GirRecord, size: number): str
103
104
 
104
105
  const renderFieldWrite = (context: ModuleContext, entry: WritableFieldSlot): string => {
105
106
  context.addRuntimeImport("t");
106
- const descriptor = context.hoistDescriptor(renderDescriptor(context, entry.field.type, "none"));
107
+
108
+ const descriptor = context.hoistDescriptor(
109
+ renderDescriptor(context, entry.field.type, "none", { isInline: isInlineField(context, entry.field) }),
110
+ );
111
+
107
112
  const name = toCamelIdentifier(entry.field.name);
108
113
 
109
114
  const write = emitFieldWrite(context, {
@@ -9,9 +9,9 @@ import { tStruct } from "../../analysis/descriptor.js";
9
9
  import { renderTsType } from "../../analysis/ts-type.js";
10
10
  import { renderJsDoc } from "../../writer/doc.js";
11
11
  import { indent, renderBlock } from "../../writer/emit.js";
12
- import { isClassStructRef } from "./class-struct-record.js";
13
12
  import { bitMask, computeRecordFieldSlots, mergeBitfield, type RecordFieldSlot } from "./record-layout.js";
14
13
  import { wrapReturnValue } from "./return-wrap.js";
14
+ import { isValueMarshalable } from "./value-marshalable.js";
15
15
 
16
16
  type FieldWriteSpec = {
17
17
  descriptor: string;
@@ -91,18 +91,39 @@ type AccessorOptions = {
91
91
  fieldType: TypeId;
92
92
  };
93
93
 
94
+ // An inline record field is read and written through its own bytes, so it can only be exposed when
95
+ // those bytes are copyable; a pointer field is marshalled as a handle and is always fine.
96
+ const isMarshalableField = (context: ModuleContext, field: GirField): boolean => {
97
+ if (field.cType?.endsWith("*") === true) {
98
+ return true;
99
+ }
100
+
101
+ const type = field.type === undefined ? undefined : context.library.typeFor(field.type);
102
+
103
+ if (type?.kind !== "record") {
104
+ return true;
105
+ }
106
+
107
+ return isValueMarshalable(context, type.namespace.name, type.value);
108
+ };
109
+
94
110
  const isEmittableField = (context: ModuleContext, field: GirField): field is GirField & { type: TypeId } =>
95
111
  !field.private &&
96
112
  field.type !== undefined &&
97
113
  !isInlineCallbackRef(context.library, field.type) &&
98
- !isClassStructRef(context, field.type);
114
+ isMarshalableField(context, field);
99
115
 
100
116
  const emitFieldWrite = (context: ModuleContext, spec: FieldWriteSpec): string => {
101
117
  const { descriptor, slot, targetExpr, valueExpr } = spec;
102
118
  context.addRuntimeImport("write");
103
119
 
104
120
  if (slot.bitWidth === undefined) {
105
- return `write(${targetExpr}, ${descriptor}, ${String(slot.byteOffset)}, ${valueExpr});`;
121
+ context.addRuntimeImport("toNative");
122
+
123
+ return (
124
+ `write(${targetExpr}, ${descriptor}, ${String(slot.byteOffset)}, ` +
125
+ `toNative(${descriptor}, ${valueExpr}));`
126
+ );
106
127
  }
107
128
 
108
129
  context.addRuntimeImport("read");
@@ -192,8 +213,10 @@ const renderRecordFieldAccessor = (
192
213
 
193
214
  // A record field whose C type is the record itself rather than a pointer to it holds the value
194
215
  // in place, so its own address is the value and no word may be loaded out of it.
195
- const isInline = resolveInlineStructFields(context, field.type, field.cType) !== undefined;
196
- const descriptor = context.hoistDescriptor(renderDescriptor(context, field.type, "none", { isInline }));
216
+ const descriptor = context.hoistDescriptor(
217
+ renderDescriptor(context, field.type, "none", { isInline: isInlineField(context, field) }),
218
+ );
219
+
197
220
  const tsType = renderTsType(context, field.type, false);
198
221
 
199
222
  const accessorOptions: AccessorOptions = {
@@ -246,6 +269,11 @@ const isAccessorEligibleType = (context: ModuleContext, ref: TypeId): boolean =>
246
269
  }
247
270
  };
248
271
 
272
+ // A field whose record is embedded by value is read and written through its own bytes, so the
273
+ // constructor and the setter have to describe it the same way.
274
+ const isInlineField = (context: ModuleContext, field: GirField): boolean =>
275
+ field.type !== undefined && resolveInlineStructFields(context, field.type, field.cType) !== undefined;
276
+
249
277
  const resolveInlineStructFields = (
250
278
  context: ModuleContext,
251
279
  ref: TypeId | undefined,
@@ -604,4 +632,11 @@ const setterBlock = (options: AccessorOptions): string =>
604
632
  setterBody(options.context, options.descriptor, options.slot),
605
633
  );
606
634
 
607
- export { isEmittableField, emitFieldWrite, resolveRecordFieldEntry, renderRecordFieldAccessor, type RecordFieldEntry };
635
+ export {
636
+ isEmittableField,
637
+ isInlineField,
638
+ emitFieldWrite,
639
+ resolveRecordFieldEntry,
640
+ renderRecordFieldAccessor,
641
+ type RecordFieldEntry,
642
+ };
@@ -3,7 +3,6 @@ import type { ModuleContext } from "../../writer/context.js";
3
3
  import { renderJsDoc } from "../../writer/doc.js";
4
4
  import { indentMembers } from "../../writer/emit.js";
5
5
  import { type Callables, dedupeCallables, generateBindings, renderPlainTypeMembers } from "./callables.js";
6
- import { isClassStructRecord } from "./class-struct-record.js";
7
6
  import { renderSourceGtype } from "./gtype-binding.js";
8
7
  import { renderRecordConstructor, renderRecordConstructorPropsInterface } from "./record-constructor.js";
9
8
  import { renderRecordFieldAccessor } from "./record-field-accessor.js";
@@ -18,18 +17,10 @@ const generateRecord = (context: ModuleContext, record: GirRecord): void => {
18
17
  return;
19
18
  }
20
19
 
21
- if (record.isVtable) {
22
- return;
23
- }
24
-
25
20
  if (record.name.length === 0) {
26
21
  return;
27
22
  }
28
23
 
29
- if (isClassStructRecord(context.library, context.namespace.name, record)) {
30
- return;
31
- }
32
-
33
24
  const className = record.name;
34
25
  const isErrorSubclass = isGErrorRecord(context, record);
35
26