@gtkx/codegen 1.5.0 → 1.6.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 (175) hide show
  1. package/dist/analysis/descriptor-render.d.ts +1 -0
  2. package/dist/analysis/descriptor-render.d.ts.map +1 -1
  3. package/dist/analysis/descriptor-render.js +56 -28
  4. package/dist/analysis/descriptor-render.js.map +1 -1
  5. package/dist/analysis/descriptor.d.ts +3 -1
  6. package/dist/analysis/descriptor.d.ts.map +1 -1
  7. package/dist/analysis/descriptor.js +10 -9
  8. package/dist/analysis/descriptor.js.map +1 -1
  9. package/dist/compile.d.ts.map +1 -1
  10. package/dist/compile.js +24 -3
  11. package/dist/compile.js.map +1 -1
  12. package/dist/docs/api-reference.d.ts +2 -0
  13. package/dist/docs/api-reference.d.ts.map +1 -1
  14. package/dist/docs/api-reference.js +2 -1
  15. package/dist/docs/api-reference.js.map +1 -1
  16. package/dist/docs/pipeline.d.ts +3 -0
  17. package/dist/docs/pipeline.d.ts.map +1 -1
  18. package/dist/docs/pipeline.js +44 -12
  19. package/dist/docs/pipeline.js.map +1 -1
  20. package/dist/fingerprint.d.ts +3 -0
  21. package/dist/fingerprint.d.ts.map +1 -1
  22. package/dist/fingerprint.js +4 -0
  23. package/dist/fingerprint.js.map +1 -1
  24. package/dist/gi.d.ts +1 -0
  25. package/dist/gi.d.ts.map +1 -1
  26. package/dist/gi.js +4 -1
  27. package/dist/gi.js.map +1 -1
  28. package/dist/gir/libraries.d.ts +12 -5
  29. package/dist/gir/libraries.d.ts.map +1 -1
  30. package/dist/gir/libraries.js +50 -14
  31. package/dist/gir/libraries.js.map +1 -1
  32. package/dist/gir/library.d.ts +5 -0
  33. package/dist/gir/library.d.ts.map +1 -1
  34. package/dist/gir/library.js +6 -0
  35. package/dist/gir/library.js.map +1 -1
  36. package/dist/internal.d.ts +1 -0
  37. package/dist/internal.d.ts.map +1 -1
  38. package/dist/internal.js +1 -0
  39. package/dist/internal.js.map +1 -1
  40. package/dist/jsx.d.ts +1 -0
  41. package/dist/jsx.d.ts.map +1 -1
  42. package/dist/jsx.js +2 -0
  43. package/dist/jsx.js.map +1 -1
  44. package/dist/runner.d.ts +2 -0
  45. package/dist/runner.d.ts.map +1 -1
  46. package/dist/runner.js +3 -1
  47. package/dist/runner.js.map +1 -1
  48. package/dist/store/gi/bootstrap.d.ts +4 -0
  49. package/dist/store/gi/bootstrap.d.ts.map +1 -0
  50. package/dist/store/gi/bootstrap.js +48 -0
  51. package/dist/store/gi/bootstrap.js.map +1 -0
  52. package/dist/store/gi/class.d.ts.map +1 -1
  53. package/dist/store/gi/class.js +75 -28
  54. package/dist/store/gi/class.js.map +1 -1
  55. package/dist/store/gi/constructor-props.d.ts.map +1 -1
  56. package/dist/store/gi/constructor-props.js +2 -1
  57. package/dist/store/gi/constructor-props.js.map +1 -1
  58. package/dist/store/gi/enum.js +3 -3
  59. package/dist/store/gi/enum.js.map +1 -1
  60. package/dist/store/gi/folded.d.ts +14 -0
  61. package/dist/store/gi/folded.d.ts.map +1 -0
  62. package/dist/store/gi/folded.js +35 -0
  63. package/dist/store/gi/folded.js.map +1 -0
  64. package/dist/store/gi/function.d.ts.map +1 -1
  65. package/dist/store/gi/function.js +22 -3
  66. package/dist/store/gi/function.js.map +1 -1
  67. package/dist/store/gi/gtype-binding.d.ts.map +1 -1
  68. package/dist/store/gi/gtype-binding.js +11 -1
  69. package/dist/store/gi/gtype-binding.js.map +1 -1
  70. package/dist/store/gi/interface.d.ts.map +1 -1
  71. package/dist/store/gi/interface.js +43 -4
  72. package/dist/store/gi/interface.js.map +1 -1
  73. package/dist/store/gi/method.d.ts.map +1 -1
  74. package/dist/store/gi/method.js +2 -0
  75. package/dist/store/gi/method.js.map +1 -1
  76. package/dist/store/gi/pipeline.d.ts +5 -1
  77. package/dist/store/gi/pipeline.d.ts.map +1 -1
  78. package/dist/store/gi/pipeline.js +13 -1
  79. package/dist/store/gi/pipeline.js.map +1 -1
  80. package/dist/store/gi/property-accessor.js +1 -1
  81. package/dist/store/gi/property-accessor.js.map +1 -1
  82. package/dist/store/gi/record-field-accessor.d.ts.map +1 -1
  83. package/dist/store/gi/record-field-accessor.js +3 -3
  84. package/dist/store/gi/record-field-accessor.js.map +1 -1
  85. package/dist/store/gi/record.d.ts.map +1 -1
  86. package/dist/store/gi/record.js +44 -12
  87. package/dist/store/gi/record.js.map +1 -1
  88. package/dist/store/gi/registration.js +2 -2
  89. package/dist/store/gi/registration.js.map +1 -1
  90. package/dist/store/gi/return-wrap.js +1 -1
  91. package/dist/store/gi/return-wrap.js.map +1 -1
  92. package/dist/store/gi/signal.d.ts.map +1 -1
  93. package/dist/store/gi/signal.js +7 -3
  94. package/dist/store/gi/signal.js.map +1 -1
  95. package/dist/store/gi/vtable.d.ts.map +1 -1
  96. package/dist/store/gi/vtable.js +1 -1
  97. package/dist/store/gi/vtable.js.map +1 -1
  98. package/dist/store/gi-store.d.ts +1 -0
  99. package/dist/store/gi-store.d.ts.map +1 -1
  100. package/dist/store/gi-store.js +16 -7
  101. package/dist/store/gi-store.js.map +1 -1
  102. package/dist/store/jsx/element-components.d.ts.map +1 -1
  103. package/dist/store/jsx/element-components.js +64 -10
  104. package/dist/store/jsx/element-components.js.map +1 -1
  105. package/dist/store/jsx/element-prop-types.d.ts +2 -0
  106. package/dist/store/jsx/element-prop-types.d.ts.map +1 -1
  107. package/dist/store/jsx/element-prop-types.js +2 -0
  108. package/dist/store/jsx/element-prop-types.js.map +1 -1
  109. package/dist/store/jsx/metadata.d.ts.map +1 -1
  110. package/dist/store/jsx/metadata.js +28 -1
  111. package/dist/store/jsx/metadata.js.map +1 -1
  112. package/dist/store/jsx-store.d.ts +1 -0
  113. package/dist/store/jsx-store.d.ts.map +1 -1
  114. package/dist/store/jsx-store.js +7 -2
  115. package/dist/store/jsx-store.js.map +1 -1
  116. package/dist/store/store-fs.d.ts +2 -1
  117. package/dist/store/store-fs.d.ts.map +1 -1
  118. package/dist/store/store-fs.js +1 -1
  119. package/dist/store/store-fs.js.map +1 -1
  120. package/dist/writer/context.d.ts +17 -0
  121. package/dist/writer/context.d.ts.map +1 -1
  122. package/dist/writer/context.js +83 -11
  123. package/dist/writer/context.js.map +1 -1
  124. package/dist/writer/emit.d.ts +2 -1
  125. package/dist/writer/emit.d.ts.map +1 -1
  126. package/dist/writer/emit.js +2 -1
  127. package/dist/writer/emit.js.map +1 -1
  128. package/dist/writer/imports.d.ts.map +1 -1
  129. package/dist/writer/imports.js +6 -1
  130. package/dist/writer/imports.js.map +1 -1
  131. package/dist/writer/module.d.ts +1 -0
  132. package/dist/writer/module.d.ts.map +1 -1
  133. package/dist/writer/module.js +6 -1
  134. package/dist/writer/module.js.map +1 -1
  135. package/overrides/glib/regex.ts.ejs +2 -0
  136. package/package.json +4 -4
  137. package/src/analysis/descriptor-render.ts +79 -30
  138. package/src/analysis/descriptor.ts +19 -11
  139. package/src/compile.ts +42 -3
  140. package/src/docs/api-reference.ts +4 -1
  141. package/src/docs/pipeline.ts +64 -13
  142. package/src/fingerprint.ts +7 -0
  143. package/src/gi.ts +6 -1
  144. package/src/gir/libraries.ts +64 -16
  145. package/src/gir/library.ts +9 -0
  146. package/src/internal.ts +1 -0
  147. package/src/jsx.ts +3 -0
  148. package/src/runner.ts +5 -1
  149. package/src/store/gi/bootstrap.ts +64 -0
  150. package/src/store/gi/class.ts +100 -29
  151. package/src/store/gi/constructor-props.ts +2 -1
  152. package/src/store/gi/enum.ts +4 -4
  153. package/src/store/gi/folded.ts +57 -0
  154. package/src/store/gi/function.ts +22 -3
  155. package/src/store/gi/gtype-binding.ts +15 -2
  156. package/src/store/gi/interface.ts +64 -7
  157. package/src/store/gi/method.ts +2 -0
  158. package/src/store/gi/pipeline.ts +21 -2
  159. package/src/store/gi/property-accessor.ts +1 -1
  160. package/src/store/gi/record-field-accessor.ts +5 -3
  161. package/src/store/gi/record.ts +61 -14
  162. package/src/store/gi/registration.ts +2 -2
  163. package/src/store/gi/return-wrap.ts +1 -1
  164. package/src/store/gi/signal.ts +8 -3
  165. package/src/store/gi/vtable.ts +1 -0
  166. package/src/store/gi-store.ts +19 -7
  167. package/src/store/jsx/element-components.ts +107 -10
  168. package/src/store/jsx/element-prop-types.ts +4 -0
  169. package/src/store/jsx/metadata.ts +41 -0
  170. package/src/store/jsx-store.ts +10 -4
  171. package/src/store/store-fs.ts +3 -2
  172. package/src/writer/context.ts +109 -16
  173. package/src/writer/emit.ts +3 -1
  174. package/src/writer/imports.ts +9 -1
  175. package/src/writer/module.ts +9 -1
@@ -62,6 +62,7 @@ type ArgIndexOptions = {
62
62
  argIndexMap: Map<number, number> | undefined;
63
63
  cursor: GirCursorBounds | undefined;
64
64
  hasOutIndirection: boolean;
65
+ isReceived: boolean;
65
66
  };
66
67
 
67
68
  type CursorArgIndexOptions = ArgIndexOptions & { cursor: GirCursorBounds };
@@ -76,11 +77,13 @@ type RenderDescriptorOptions = Partial<ArgIndexOptions> & {
76
77
  type RecordPlacement = {
77
78
  isCallerAllocated?: boolean;
78
79
  isInline?: boolean;
80
+ isReceived?: boolean;
79
81
  };
80
82
 
81
83
  type RecordLayout = {
82
84
  isCallerAllocated: boolean;
83
85
  isInline: boolean;
86
+ isReceived: boolean;
84
87
  };
85
88
 
86
89
  type FundamentalDescriptor = {
@@ -90,6 +93,7 @@ type FundamentalDescriptor = {
90
93
  typeName: string | undefined;
91
94
  ownership: Ownership;
92
95
  wrapperClass?: string | undefined;
96
+ fallbackClass?: string | undefined;
93
97
  isCallerAllocated?: boolean | undefined;
94
98
  isInline?: boolean | undefined;
95
99
  };
@@ -110,6 +114,7 @@ type FundamentalRecordOptions = {
110
114
  unrefFunc: string;
111
115
  ownership: Ownership;
112
116
  wrapperClass: string | undefined;
117
+ fallbackClass?: string | undefined;
113
118
  isCallerAllocated: boolean;
114
119
  isInline: boolean;
115
120
  };
@@ -169,6 +174,7 @@ const argIndexOptions = (options: RenderDescriptorOptions): ArgIndexOptions => (
169
174
  argIndexMap: options.argIndexMap,
170
175
  cursor: options.cursor,
171
176
  hasOutIndirection: options.hasOutIndirection === true,
177
+ isReceived: options.isReceived === true,
172
178
  });
173
179
 
174
180
  const renderDescriptor = (
@@ -310,7 +316,12 @@ const renderParamDescriptor = (
310
316
  ref: TypeId | undefined,
311
317
  argIndex: Partial<ArgIndexOptions> = {},
312
318
  ): string => {
313
- const behindRef: RenderDescriptorOptions = { ...argIndex, cursor: parameter.cursor, hasOutIndirection: true };
319
+ const behindRef: RenderDescriptorOptions = {
320
+ ...argIndex,
321
+ cursor: parameter.cursor,
322
+ hasOutIndirection: true,
323
+ isReceived: true,
324
+ };
314
325
 
315
326
  if (isCellInout(context.library, parameter)) {
316
327
  return tRef(renderDescriptor(context, ref, parameter.transferOwnership, behindRef), true);
@@ -323,6 +334,7 @@ const renderParamDescriptor = (
323
334
  return renderDescriptor(context, ref, parameter.transferOwnership, {
324
335
  ...argIndex,
325
336
  isCallerAllocated: isCallerAllocatedOut(parameter) || isRecordInout(context, parameter),
337
+ isReceived: true,
326
338
  });
327
339
  };
328
340
 
@@ -394,7 +406,7 @@ const renderCallbackType = (
394
406
 
395
407
  return tCallback({
396
408
  argTypes,
397
- returns: renderDescriptor(context, returnValue.type, returnValue.transferOwnership),
409
+ returns: renderDescriptor(context, returnValue.type, returnValue.transferOwnership, { isReceived: true }),
398
410
  options: callbackOptionsArg(owningParameter, findUserDataIndex(callback.parameters), callback.throws),
399
411
  });
400
412
  };
@@ -420,17 +432,28 @@ const primitiveExpression = (category: PrimitiveCategory, ownership: Ownership):
420
432
  };
421
433
 
422
434
  const renderFundamental = (descriptor: FundamentalDescriptor): string => {
423
- const { lib, refFunc, unrefFunc, typeName, ownership, wrapperClass, isCallerAllocated, isInline } = descriptor;
435
+ const { lib, refFunc, unrefFunc, typeName, ownership, wrapperClass, fallbackClass } = descriptor;
424
436
 
425
437
  return tFundamental(lib, refFunc, unrefFunc, {
426
438
  ownership,
427
439
  typeName,
428
440
  wrapperClass,
429
- isCallerAllocated,
430
- isInline,
441
+ fallbackClass,
442
+ isCallerAllocated: descriptor.isCallerAllocated,
443
+ isInline: descriptor.isInline,
431
444
  });
432
445
  };
433
446
 
447
+ const fallbackClassThunk = (
448
+ context: ModuleContext,
449
+ namespaceName: string,
450
+ name: string,
451
+ isReceived: boolean,
452
+ ): string | undefined =>
453
+ isReceived && context.library.isTreeShaken
454
+ ? `() => ${context.qualify(namespaceName, sanitizeTypeIdentifier(name))}`
455
+ : undefined;
456
+
434
457
  const sunkOwnership = (
435
458
  ancestor: AncestorFundamental,
436
459
  ownership: Ownership,
@@ -447,15 +470,20 @@ const classOrInterfaceExpression = (
447
470
  context: ModuleContext,
448
471
  resolved: Extract<EntityType, { kind: "class" | "interface" }>,
449
472
  ownership: Ownership,
450
- isNewlyCreated: boolean,
473
+ options: { isNewlyCreated: boolean; isReceived: boolean },
451
474
  ): string => {
452
475
  const ancestor = fundamentalAncestor(context, resolved);
476
+ const fallbackClass = fallbackClassThunk(context, resolved.namespace.name, resolved.value.name, options.isReceived);
453
477
 
454
478
  if (ancestor === undefined) {
455
- return tObject(ownership);
479
+ return tObject(ownership, fallbackClass);
456
480
  }
457
481
 
458
- return renderFundamental({ ...ancestor, ownership: sunkOwnership(ancestor, ownership, isNewlyCreated) });
482
+ return renderFundamental({
483
+ ...ancestor,
484
+ ownership: sunkOwnership(ancestor, ownership, options.isNewlyCreated),
485
+ fallbackClass,
486
+ });
459
487
  };
460
488
 
461
489
  const isClassOrInterface = (type: GirType | undefined): type is Extract<EntityType, { kind: "class" | "interface" }> =>
@@ -552,6 +580,7 @@ const renderSelfDescriptor = (context: ModuleContext, instance: GirParameter): s
552
580
  const recordLayout = (placement: RecordPlacement): RecordLayout => ({
553
581
  isCallerAllocated: placement.isCallerAllocated ?? false,
554
582
  isInline: placement.isInline ?? false,
583
+ isReceived: placement.isReceived === true,
555
584
  });
556
585
 
557
586
  const recordRefPair = (record: ResolvedRecord): { refFunc: string | undefined; unrefFunc: string | undefined } => ({
@@ -588,7 +617,7 @@ const structExpression = (
588
617
  };
589
618
 
590
619
  const fundamentalRecordExpression = (options: FundamentalRecordOptions): string => {
591
- const { resolved, lib, refFunc, unrefFunc, ownership, wrapperClass, isCallerAllocated, isInline } = options;
620
+ const { resolved, lib, refFunc, unrefFunc, ownership, wrapperClass, fallbackClass } = options;
592
621
 
593
622
  return renderFundamental({
594
623
  lib,
@@ -597,8 +626,9 @@ const fundamentalRecordExpression = (options: FundamentalRecordOptions): string
597
626
  typeName: resolved.value.glibTypeName,
598
627
  ownership,
599
628
  wrapperClass,
600
- isCallerAllocated,
601
- isInline,
629
+ fallbackClass,
630
+ isCallerAllocated: options.isCallerAllocated,
631
+ isInline: options.isInline,
602
632
  });
603
633
  };
604
634
 
@@ -608,6 +638,7 @@ const boxedRecordExpression = (options: {
608
638
  ownership: Ownership;
609
639
  isCallerAllocated: boolean;
610
640
  isInline: boolean;
641
+ isReceived: boolean;
611
642
  typeFnName: string;
612
643
  }): string => {
613
644
  const { context, resolved, ownership, isCallerAllocated, isInline, typeFnName } = options;
@@ -623,6 +654,7 @@ const boxedRecordExpression = (options: {
623
654
  isCallerAllocated,
624
655
  isInline,
625
656
  size: size > 0 ? size : undefined,
657
+ fallbackClass: fallbackClassThunk(context, resolved.namespace.name, record.name, options.isReceived),
626
658
  });
627
659
  };
628
660
 
@@ -653,35 +685,48 @@ const isPlainStruct = (resolved: Extract<EntityType, { kind: "record" }>): boole
653
685
  return record.glibGetType === undefined;
654
686
  };
655
687
 
656
- const recordExpression = (
688
+ const fundamentalRecordPath = (
657
689
  context: ModuleContext,
658
690
  resolved: Extract<EntityType, { kind: "record" }>,
659
691
  ownership: Ownership,
660
- placement: RecordPlacement = {},
661
- ): string => {
692
+ placement: RecordPlacement,
693
+ ): string | undefined => {
662
694
  const record = resolved.value;
663
695
  const { refFunc, unrefFunc } = recordRefPair(record);
664
696
  const lib = resolved.namespace.sharedLibrary;
665
697
 
666
- if (refFunc !== undefined && unrefFunc !== undefined && lib !== undefined) {
667
- const wrapperClass = requiresFallbackClass(record)
668
- ? context.qualify(resolved.namespace.name, sanitizeTypeIdentifier(record.name))
669
- : undefined;
670
-
671
- return fundamentalRecordExpression({
672
- resolved,
673
- lib,
674
- refFunc,
675
- unrefFunc,
676
- ownership,
677
- wrapperClass,
678
- ...recordLayout(placement),
679
- });
698
+ if (refFunc === undefined || unrefFunc === undefined || lib === undefined) {
699
+ return undefined;
680
700
  }
681
701
 
682
- return plainRecordExpression(context, resolved, ownership, placement);
702
+ const wrapperClass = requiresFallbackClass(record)
703
+ ? context.qualify(resolved.namespace.name, sanitizeTypeIdentifier(record.name))
704
+ : undefined;
705
+
706
+ return fundamentalRecordExpression({
707
+ resolved,
708
+ lib,
709
+ refFunc,
710
+ unrefFunc,
711
+ ownership,
712
+ wrapperClass,
713
+ fallbackClass:
714
+ wrapperClass === undefined
715
+ ? fallbackClassThunk(context, resolved.namespace.name, record.name, placement.isReceived === true)
716
+ : undefined,
717
+ ...recordLayout(placement),
718
+ });
683
719
  };
684
720
 
721
+ const recordExpression = (
722
+ context: ModuleContext,
723
+ resolved: Extract<EntityType, { kind: "record" }>,
724
+ ownership: Ownership,
725
+ placement: RecordPlacement = {},
726
+ ): string =>
727
+ fundamentalRecordPath(context, resolved, ownership, placement) ??
728
+ plainRecordExpression(context, resolved, ownership, placement);
729
+
685
730
  const rawEnumDescriptor = (isSigned: boolean): string => (isSigned ? tInt32 : tUint32);
686
731
 
687
732
  const flagsMask = (resolved: Extract<EntityType, { kind: "enum" }>): number =>
@@ -711,12 +756,16 @@ const expressionForResolved = (
711
756
  switch (resolved.kind) {
712
757
  case "class":
713
758
  case "interface": {
714
- return classOrInterfaceExpression(context, resolved, ownership, options.isNewlyCreated ?? false);
759
+ return classOrInterfaceExpression(context, resolved, ownership, {
760
+ isNewlyCreated: options.isNewlyCreated ?? false,
761
+ isReceived: options.isReceived === true,
762
+ });
715
763
  }
716
764
  case "record": {
717
765
  return recordExpression(context, resolved, ownership, {
718
766
  isCallerAllocated: options.isCallerAllocated ?? false,
719
767
  isInline: options.isInline ?? false,
768
+ isReceived: options.isReceived === true,
720
769
  });
721
770
  }
722
771
  case "enum": {
@@ -1,6 +1,6 @@
1
1
  import { sourceStringLiteral } from "@gtkx/utils";
2
2
  import type { GirCursorBounds } from "../gir/parameter.js";
3
- import { joinArgs } from "../writer/emit.js";
3
+ import { joinArgs, pure } from "../writer/emit.js";
4
4
 
5
5
  type Ownership = "borrowed" | "full";
6
6
 
@@ -70,6 +70,7 @@ type BoxedOptions = {
70
70
  isCallerAllocated: boolean;
71
71
  isInline?: boolean;
72
72
  size: number | undefined;
73
+ fallbackClass?: string | undefined;
73
74
  };
74
75
 
75
76
  type StructOptions = {
@@ -83,6 +84,7 @@ type FundamentalOptions = {
83
84
  ownership: Ownership;
84
85
  typeName: string | undefined;
85
86
  wrapperClass: string | undefined;
87
+ fallbackClass?: string | undefined;
86
88
  isCallerAllocated?: boolean | undefined;
87
89
  isInline?: boolean | undefined;
88
90
  };
@@ -178,26 +180,29 @@ const optionsObject = (parts: (string | undefined)[]): string | undefined => {
178
180
  return present.length === 0 ? undefined : `{ ${present.join(", ")} }`;
179
181
  };
180
182
 
183
+ const optionalLiteralEntry = (key: string, value: string | undefined): string | undefined =>
184
+ value === undefined ? undefined : `${key}: ${sourceStringLiteral(value)}`;
185
+
181
186
  const tScalar = (name: ScalarDescriptorName): string => T[name];
182
187
 
183
188
  const tString = (ownership: Ownership, length?: string): string =>
184
189
  call("string", [sourceStringLiteral(ownership), length]);
185
190
 
186
- const tObject = (ownership: Ownership): string => call("object", [sourceStringLiteral(ownership)]);
191
+ const tObject = (ownership: Ownership, fallbackClass?: string): string =>
192
+ call("object", [sourceStringLiteral(ownership), fallbackClass]);
187
193
 
188
194
  const tBoxed = (glibName: string, options: BoxedOptions): string =>
189
195
  call("boxed", [
190
196
  sourceStringLiteral(glibName),
191
197
  optionsObject([
192
198
  `ownership: ${sourceStringLiteral(options.ownership)}`,
193
- options.sharedLibrary === undefined
194
- ? undefined
195
- : `sharedLibrary: ${sourceStringLiteral(options.sharedLibrary)}`,
199
+ optionalLiteralEntry("sharedLibrary", options.sharedLibrary),
196
200
  `getTypeFnName: ${sourceStringLiteral(options.getTypeFnName)}`,
197
- options.freeFnName === undefined ? undefined : `freeFnName: ${sourceStringLiteral(options.freeFnName)}`,
201
+ optionalLiteralEntry("freeFnName", options.freeFnName),
198
202
  options.isCallerAllocated ? "isCallerAllocated: true" : undefined,
199
203
  options.isInline === true ? "isInline: true" : undefined,
200
204
  options.size === undefined ? undefined : `size: ${String(options.size)}`,
205
+ options.fallbackClass === undefined ? undefined : `fallbackClass: ${options.fallbackClass}`,
201
206
  ]),
202
207
  ]);
203
208
 
@@ -224,6 +229,7 @@ const tFundamental = (lib: string, refFunc: string, unrefFunc: string, options:
224
229
  `ownership: ${sourceStringLiteral(options.ownership)}`,
225
230
  options.typeName === undefined ? undefined : `typeName: ${sourceStringLiteral(options.typeName)}`,
226
231
  options.wrapperClass === undefined ? undefined : `wrapperClass: ${options.wrapperClass}`,
232
+ options.fallbackClass === undefined ? undefined : `fallbackClass: ${options.fallbackClass}`,
227
233
  options.isCallerAllocated === true ? "isCallerAllocated: true" : undefined,
228
234
  options.isInline === true ? "isInline: true" : undefined,
229
235
  ]),
@@ -327,11 +333,13 @@ const tFn = (lib: string, cIdentifier: string, spec: FnSpecParts): string => {
327
333
  const unpackEntry = spec.isReturnUnpacked ? `, ${UNPACKED_RETURN_ENTRY}` : "";
328
334
  const throwsEntry = spec.canThrow ? ", canThrow: true" : "";
329
335
 
330
- return call("fn", [
331
- sourceStringLiteral(lib),
332
- sourceStringLiteral(cIdentifier),
333
- `{ args: ${spec.args}, returns: ${spec.returns}${skipEntry}${unpackEntry}${throwsEntry} }`,
334
- ]);
336
+ return pure(
337
+ call("fn", [
338
+ sourceStringLiteral(lib),
339
+ sourceStringLiteral(cIdentifier),
340
+ `() => ({ args: ${spec.args}, returns: ${spec.returns}${skipEntry}${unpackEntry}${throwsEntry} })`,
341
+ ]),
342
+ );
335
343
  };
336
344
 
337
345
  export {
package/src/compile.ts CHANGED
@@ -76,9 +76,12 @@ const DECLARATION_EMIT = {
76
76
  };
77
77
 
78
78
  const MODULE_EMIT = {
79
- removeComments: true,
79
+ removeComments: false,
80
80
  };
81
81
 
82
+ const PURE_ANNOTATION_TEXT = "@__PURE__";
83
+ const PURE_ANNOTATION_COMMENT = ` ${PURE_ANNOTATION_TEXT} `;
84
+
82
85
  const DECLARATION_OPTIONS = ts.convertCompilerOptionsFromJson(
83
86
  { ...BASE_COMPILER_OPTIONS, ...DECLARATION_EMIT },
84
87
  ".",
@@ -213,7 +216,7 @@ const diagnosticError = (params: ProjectContext, diagnostics: ts.Diagnostic[]):
213
216
 
214
217
  const lines = [...diagnosed.map((entry) => entry.text), ...originLines(diagnosed, params.files)];
215
218
 
216
- return new Error(`Compiling ${label} found ${String(diagnosed.length)} error(s):\n${lines.join("\n")}`);
219
+ return new Error(`Compiling ${label} failed:\n${lines.join("\n")}`);
217
220
  };
218
221
 
219
222
  const keepFailedProject = (input: FailedProjectInput): Error => {
@@ -266,10 +269,46 @@ const transpileOptions = (compilerOptions: ts.CompilerOptions, fileName: string)
266
269
  reportDiagnostics: true,
267
270
  });
268
271
 
272
+ const isPureAnnotated = (text: string, node: ts.Node): boolean =>
273
+ ts.isCallExpression(node) &&
274
+ node.pos >= 0 &&
275
+ (ts.getLeadingCommentRanges(text, node.pos) ?? []).some((range) =>
276
+ text.slice(range.pos, range.end).includes(PURE_ANNOTATION_TEXT),
277
+ );
278
+
279
+ const dropCommentsExceptPureAnnotations =
280
+ (): ts.TransformerFactory<ts.SourceFile> => (context) => (sourceFile) => {
281
+ const { text } = sourceFile;
282
+
283
+ const visit = (node: ts.Node): ts.Node => {
284
+ const isPure = isPureAnnotated(text, node);
285
+ const visited = ts.visitEachChild(node, visit, context);
286
+ ts.setEmitFlags(visited, ts.EmitFlags.NoLeadingComments | ts.EmitFlags.NoTrailingComments);
287
+
288
+ if (isPure) {
289
+ ts.addSyntheticLeadingComment(
290
+ visited,
291
+ ts.SyntaxKind.MultiLineCommentTrivia,
292
+ PURE_ANNOTATION_COMMENT,
293
+ false,
294
+ );
295
+ }
296
+
297
+ return visited;
298
+ };
299
+
300
+ return ts.visitEachChild(sourceFile, visit, context);
301
+ };
302
+
269
303
  const emitModule = (module: SourceModule, projectDir: string): ts.Diagnostic[] => {
270
304
  const fileName = join(projectDir, module.fileName);
271
305
  const declaration = ts.transpileDeclaration(module.source, transpileOptions(DECLARATION_OPTIONS, fileName));
272
- const javascript = ts.transpileModule(module.source, transpileOptions(MODULE_OPTIONS, fileName));
306
+
307
+ const javascript = ts.transpileModule(module.source, {
308
+ ...transpileOptions(MODULE_OPTIONS, fileName),
309
+ transformers: { after: [dropCommentsExceptPureAnnotations()] },
310
+ });
311
+
273
312
  const diagnostics = [...declaration.diagnostics ?? [], ...javascript.diagnostics ?? []];
274
313
 
275
314
  if (diagnostics.length > 0) {
@@ -33,6 +33,8 @@ type ApiReferenceOptions = {
33
33
  isFinishTrimmed?: boolean;
34
34
  /** Describes a handle-passing inout parameter as mutated in place rather than returned; off by default. */
35
35
  isInoutInPlace?: boolean;
36
+ /** Folds registrations into each class so bundlers can drop unused ones; off by default. */
37
+ isTreeShaken?: boolean;
36
38
  };
37
39
 
38
40
  /** Narrows an `ApiReference.symbols` enumeration. */
@@ -386,6 +388,7 @@ class ApiReference {
386
388
  isValueUnwrapped: options.isValueUnwrapped === true,
387
389
  isFinishTrimmed: options.isFinishTrimmed === true,
388
390
  isInoutInPlace: options.isInoutInPlace === true,
391
+ isTreeShaken: options.isTreeShaken === true,
389
392
  });
390
393
 
391
394
  this.elementContext = { library: this.library, linkFor: (): string | undefined => undefined };
@@ -679,7 +682,7 @@ class ApiReference {
679
682
 
680
683
  lines.push(
681
684
  "",
682
- `## ${KIND_SECTION_TITLES[kind]} (${String(names.length)})`,
685
+ `## ${KIND_SECTION_TITLES[kind]}`,
683
686
  "",
684
687
  sortStrings(names)
685
688
  .map((symbolName) => `\`${symbolName}\``)
@@ -30,11 +30,14 @@ type DocsNamespace = {
30
30
  elements: DocsElementLink[];
31
31
  };
32
32
 
33
+ type DocsLinkStyle = "url" | "file";
34
+
33
35
  type DocsOptions = {
34
36
  libraries: string[];
35
37
  girPath: string[];
36
38
  outDir: string;
37
39
  basePath?: string;
40
+ linkStyle?: DocsLinkStyle;
38
41
  props?: ElementProps;
39
42
  omittedProps?: OmittedProps;
40
43
  isForced?: boolean;
@@ -42,6 +45,7 @@ type DocsOptions = {
42
45
  isValueUnwrapped?: boolean;
43
46
  isFinishTrimmed?: boolean;
44
47
  isInoutInPlace?: boolean;
48
+ isTreeShaken?: boolean;
45
49
  };
46
50
 
47
51
  type DocsManifest = {
@@ -75,9 +79,7 @@ const namespaceIndexPage = (namespace: DocsNamespace, elements: GlibNamedClass[]
75
79
  return `| [${entry.glibName}](${link}) | ${description} |`;
76
80
  });
77
81
 
78
- const description =
79
- `Reference pages for the ${String(namespace.elements.length)} JSX elements in ` +
80
- `the ${namespace.name} namespace.`;
82
+ const description = `Reference pages for the JSX elements in the ${namespace.name} namespace.`;
81
83
 
82
84
  return [
83
85
  "---",
@@ -96,7 +98,41 @@ const namespaceIndexPage = (namespace: DocsNamespace, elements: GlibNamedClass[]
96
98
  ].join("\n");
97
99
  };
98
100
 
99
- const rootIndexPage = (namespaces: DocsNamespace[], libraries: string[]): string => {
101
+ const fileIndexPage = (namespaces: DocsNamespace[], libraries: string[]): string => {
102
+ const rows = namespaces.map(
103
+ (ns) => `| ${ns.name} | \`@gtkx/jsx/${ns.directory}\` | ${String(ns.elements.length)} | ${ns.link} |`,
104
+ );
105
+
106
+ const librariesList = libraries.map((library) => `\`${library}\``).join(", ");
107
+
108
+ return [
109
+ "# Element reference",
110
+ "",
111
+ `Every JSX element generated from ${librariesList} by \`gtkx codegen\` has a page here, regenerated ` +
112
+ "whenever the GIR libraries or the project's element configuration change. These pages describe " +
113
+ "this project's bindings exactly, so they are the authority on props, signals, and method signatures.",
114
+ "",
115
+ "Every path here is from the project root, ready to read as-is.",
116
+ "",
117
+ "One page per element, at `<namespace>/<element>.md`, where the element file name is its class name in " +
118
+ "kebab-case without the namespace prefix: `GtkButton` is `gtk/button.md`, `AdwHeaderBar` is " +
119
+ "`adw/header-bar.md`. Read a page directly by that path rather than searching for it.",
120
+ "",
121
+ "Each page lists the element's props (GObject properties plus what GTKX adds, with types and defaults), " +
122
+ "its signals as `on<Signal>` handler props with exact signatures, and the methods reachable through `ref`.",
123
+ "",
124
+ "| Namespace | Import | Elements | Index |",
125
+ "| --- | --- | --- | --- |",
126
+ ...rows,
127
+ "",
128
+ ].join("\n");
129
+ };
130
+
131
+ const rootIndexPage = (namespaces: DocsNamespace[], libraries: string[], linkStyle: DocsLinkStyle): string => {
132
+ if (linkStyle === "file") {
133
+ return fileIndexPage(namespaces, libraries);
134
+ }
135
+
100
136
  const rows = namespaces.map(
101
137
  (ns) => `| [${ns.name}](${ns.link}) | \`@gtkx/jsx/${ns.directory}\` | ${String(ns.elements.length)} |`,
102
138
  );
@@ -143,12 +179,17 @@ const groupElementsByNamespace = (elements: GlibNamedClass[]): Map<string, GlibN
143
179
  return byNamespace;
144
180
  };
145
181
 
146
- const buildElementLinks = (elements: GlibNamedClass[], basePath: string): Map<string, string> => {
182
+ const buildElementLinks = (
183
+ elements: GlibNamedClass[],
184
+ basePath: string,
185
+ linkStyle: DocsLinkStyle,
186
+ ): Map<string, string> => {
147
187
  const linkByGlibName: Map<string, string> = new Map();
188
+ const suffix = linkStyle === "file" ? ".md" : "";
148
189
 
149
190
  for (const entry of elements) {
150
191
  const directory = namespaceDirectory(entry.namespace);
151
- const link = `${basePath}/${directory}/${elementSlug(entry.klass.name)}`;
192
+ const link = `${basePath}/${directory}/${elementSlug(entry.klass.name)}${suffix}`;
152
193
 
153
194
  if (linkByGlibName.values().some((existing) => existing === link)) {
154
195
  throw new Error(`Docs page slug collision for ${entry.glibName} at ${link}`);
@@ -186,16 +227,17 @@ const namespacePages = (input: {
186
227
  name: string;
187
228
  elements: GlibNamedClass[];
188
229
  basePath: string;
230
+ linkStyle: DocsLinkStyle;
189
231
  linkByGlibName: Map<string, string>;
190
232
  pageContext: ElementPageContext;
191
233
  }): NamespacePages => {
192
- const { name, elements, basePath, linkByGlibName, pageContext } = input;
234
+ const { name, elements, basePath, linkStyle, linkByGlibName, pageContext } = input;
193
235
  const directory = ownedEntry(namespaceDirectory({ name }), `the GIR namespace ${name}`);
194
236
 
195
237
  const docs: DocsNamespace = {
196
238
  name,
197
239
  directory,
198
- link: `${basePath}/${directory}/`,
240
+ link: linkStyle === "file" ? `${basePath}/${directory}/index.md` : `${basePath}/${directory}/`,
199
241
  elements: elements.map((entry) => ({ text: entry.glibName, link: linkByGlibName.get(entry.glibName) ?? "" })),
200
242
  };
201
243
 
@@ -209,10 +251,15 @@ const namespacePages = (input: {
209
251
  return { docs, pages };
210
252
  };
211
253
 
212
- const generatePages = (options: DocsOptions, basePath: string, library: Library): GeneratedDocs => {
254
+ const generatePages = (
255
+ options: DocsOptions,
256
+ basePath: string,
257
+ linkStyle: DocsLinkStyle,
258
+ library: Library,
259
+ ): GeneratedDocs => {
213
260
  const intrinsicElements = collectIntrinsicElementClasses(library);
214
261
  const byNamespace = groupElementsByNamespace(intrinsicElements);
215
- const linkByGlibName = buildElementLinks(intrinsicElements, basePath);
262
+ const linkByGlibName = buildElementLinks(intrinsicElements, basePath, linkStyle);
216
263
  const pageContext: ElementPageContext = { library, linkFor: (glibName) => linkByGlibName.get(glibName) };
217
264
  const pages: Page[] = [];
218
265
  const namespaces: DocsNamespace[] = [];
@@ -220,12 +267,12 @@ const generatePages = (options: DocsOptions, basePath: string, library: Library)
220
267
 
221
268
  for (const name of orderedNames) {
222
269
  const elements = sortStringsBy(byNamespace.get(name) ?? [], (entry) => entry.glibName);
223
- const result = namespacePages({ name, elements, basePath, linkByGlibName, pageContext });
270
+ const result = namespacePages({ name, elements, basePath, linkStyle, linkByGlibName, pageContext });
224
271
  pages.push(...result.pages);
225
272
  namespaces.push(result.docs);
226
273
  }
227
274
 
228
- pages.push({ path: ROOT_INDEX_FILENAME, content: rootIndexPage(namespaces, options.libraries) });
275
+ pages.push({ path: ROOT_INDEX_FILENAME, content: rootIndexPage(namespaces, options.libraries, linkStyle) });
229
276
 
230
277
  return { pages, namespaces };
231
278
  };
@@ -267,6 +314,7 @@ const giInputs = (options: DocsOptions, girFiles: string[]): GiInputs => ({
267
314
  isValueUnwrapped: options.isValueUnwrapped === true,
268
315
  isFinishTrimmed: options.isFinishTrimmed === true,
269
316
  isInoutInPlace: options.isInoutInPlace === true,
317
+ isTreeShaken: options.isTreeShaken === true,
270
318
  });
271
319
 
272
320
  const assertOwnedOutDir = (options: DocsOptions, manifest: DocsManifest | undefined): void => {
@@ -334,11 +382,13 @@ const writePages = (outDir: string, pages: Page[]): void => {
334
382
 
335
383
  const docsFingerprintInput = (options: DocsOptions): DocsFingerprintInput => ({
336
384
  basePath: options.basePath ?? DEFAULT_BASE_PATH,
385
+ linkStyle: options.linkStyle ?? "url",
337
386
  props: options.props ?? {},
338
387
  omittedProps: options.omittedProps ?? {},
339
388
  });
340
389
 
341
390
  const writeDocs = (options: DocsOptions): DocsResult => {
391
+ const linkStyle = options.linkStyle ?? "url";
342
392
  const input = docsFingerprintInput(options);
343
393
  setElementProps(input.props);
344
394
  setOmittedProps(input.omittedProps);
@@ -357,9 +407,10 @@ const writeDocs = (options: DocsOptions): DocsResult => {
357
407
  isValueUnwrapped: options.isValueUnwrapped === true,
358
408
  isFinishTrimmed: options.isFinishTrimmed === true,
359
409
  isInoutInPlace: options.isInoutInPlace === true,
410
+ isTreeShaken: options.isTreeShaken === true,
360
411
  });
361
412
 
362
- const { pages, namespaces } = generatePages(options, input.basePath, library);
413
+ const { pages, namespaces } = generatePages(options, input.basePath, linkStyle, library);
363
414
  clearOutDir(options, previous);
364
415
  mkdirSync(options.outDir, { recursive: true });
365
416
  const manifest: DocsManifest = { generator: MANIFEST_GENERATOR, namespaces };
@@ -17,6 +17,7 @@ type GiInputs = {
17
17
  isValueUnwrapped: boolean;
18
18
  isFinishTrimmed: boolean;
19
19
  isInoutInPlace: boolean;
20
+ isTreeShaken: boolean;
20
21
  };
21
22
 
22
23
  type GiFingerprint = {
@@ -30,6 +31,7 @@ type ModuleExport = { module: string; export: string };
30
31
 
31
32
  type DocsFingerprintInput = {
32
33
  basePath: string;
34
+ linkStyle: string;
33
35
  props: Record<string, ModuleExport>;
34
36
  omittedProps: Record<string, string[]>;
35
37
  };
@@ -46,6 +48,7 @@ type JsxFingerprintInput = {
46
48
  lazyElements: string[];
47
49
  props: Record<string, ModuleExport>;
48
50
  omittedProps: Record<string, string[]>;
51
+ isTreeShaken: boolean;
49
52
  };
50
53
 
51
54
  type JsxFingerprint = {
@@ -206,6 +209,8 @@ const hashGi = (inputs: GiInputs): string => {
206
209
  hash.update("\n");
207
210
  hash.update(String(inputs.isInoutInPlace));
208
211
  hash.update("\n");
212
+ hash.update(String(inputs.isTreeShaken));
213
+ hash.update("\n");
209
214
  hash.update(String(inputs.storeVersion));
210
215
  const girFiles = sortOrdinal(inputs.girFiles);
211
216
 
@@ -270,6 +275,7 @@ const hashDocs = (giValue: string, input: DocsFingerprintInput): string =>
270
275
  JSON.stringify([
271
276
  giValue,
272
277
  input.basePath,
278
+ input.linkStyle,
273
279
  serializeModuleExports(input.props),
274
280
  serializeOmittedProps(input.omittedProps),
275
281
  ]),
@@ -311,6 +317,7 @@ const hashJsx = (input: JsxFingerprintInput): string =>
311
317
  sortOrdinal(input.lazyElements),
312
318
  serializeModuleExports(input.props),
313
319
  serializeOmittedProps(input.omittedProps),
320
+ input.isTreeShaken,
314
321
  ]),
315
322
  )
316
323
  .digest("hex");