objcjs-types 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/bin/objcjs-types.ts +31 -0
  2. package/dist/AVFoundation/functions.d.ts +21 -0
  3. package/dist/AVFoundation/functions.js +32 -0
  4. package/dist/Accessibility/functions.d.ts +16 -0
  5. package/dist/Accessibility/functions.js +35 -0
  6. package/dist/AddressBook/functions.d.ts +98 -0
  7. package/dist/AddressBook/functions.js +290 -0
  8. package/dist/AppKit/functions.d.ts +112 -0
  9. package/dist/AppKit/functions.js +272 -0
  10. package/dist/AudioToolbox/functions.d.ts +377 -0
  11. package/dist/AudioToolbox/functions.js +1124 -0
  12. package/dist/AuthenticationServices/functions.d.ts +2 -0
  13. package/dist/AuthenticationServices/functions.js +5 -0
  14. package/dist/BrowserEngineCore/functions.d.ts +3 -0
  15. package/dist/BrowserEngineCore/functions.js +11 -0
  16. package/dist/CoreAudio/functions.d.ts +60 -0
  17. package/dist/CoreAudio/functions.js +173 -0
  18. package/dist/CoreMIDI/functions.d.ts +96 -0
  19. package/dist/CoreMIDI/functions.js +287 -0
  20. package/dist/CoreML/functions.d.ts +2 -0
  21. package/dist/CoreML/functions.js +5 -0
  22. package/dist/CoreMediaIO/functions.d.ts +38 -0
  23. package/dist/CoreMediaIO/functions.js +107 -0
  24. package/dist/CoreText/functions.d.ts +209 -0
  25. package/dist/CoreText/functions.js +611 -0
  26. package/dist/CoreWLAN/functions.d.ts +23 -0
  27. package/dist/CoreWLAN/functions.js +56 -0
  28. package/dist/DeviceDiscoveryExtension/functions.d.ts +11 -0
  29. package/dist/DeviceDiscoveryExtension/functions.js +17 -0
  30. package/dist/DiscRecording/functions.d.ts +97 -0
  31. package/dist/DiscRecording/functions.js +290 -0
  32. package/dist/DiscRecordingUI/functions.d.ts +13 -0
  33. package/dist/DiscRecordingUI/functions.js +38 -0
  34. package/dist/ExceptionHandling/functions.d.ts +1 -0
  35. package/dist/ExceptionHandling/functions.js +5 -0
  36. package/dist/FSKit/functions.d.ts +4 -0
  37. package/dist/FSKit/functions.js +11 -0
  38. package/dist/Foundation/functions.d.ts +145 -0
  39. package/dist/Foundation/functions.js +386 -0
  40. package/dist/GLKit/functions.d.ts +51 -0
  41. package/dist/GLKit/functions.js +146 -0
  42. package/dist/GameController/functions.d.ts +18 -0
  43. package/dist/GameController/functions.js +44 -0
  44. package/dist/HealthKit/functions.d.ts +19 -0
  45. package/dist/HealthKit/functions.js +35 -0
  46. package/dist/IOSurface/functions.d.ts +53 -0
  47. package/dist/IOSurface/functions.js +155 -0
  48. package/dist/IOUSBHost/functions.d.ts +44 -0
  49. package/dist/IOUSBHost/functions.js +131 -0
  50. package/dist/InstantMessage/functions.d.ts +1 -0
  51. package/dist/InstantMessage/functions.js +5 -0
  52. package/dist/JavaRuntimeSupport/functions.d.ts +40 -0
  53. package/dist/JavaRuntimeSupport/functions.js +113 -0
  54. package/dist/JavaScriptCore/functions.d.ts +120 -0
  55. package/dist/JavaScriptCore/functions.js +359 -0
  56. package/dist/MLCompute/functions.d.ts +27 -0
  57. package/dist/MLCompute/functions.js +41 -0
  58. package/dist/MapKit/functions.d.ts +23 -0
  59. package/dist/MapKit/functions.js +56 -0
  60. package/dist/Matter/functions.d.ts +17 -0
  61. package/dist/Matter/functions.js +26 -0
  62. package/dist/MediaAccessibility/functions.d.ts +28 -0
  63. package/dist/MediaAccessibility/functions.js +83 -0
  64. package/dist/MediaPlayer/functions.d.ts +3 -0
  65. package/dist/MediaPlayer/functions.js +11 -0
  66. package/dist/Metal/functions.d.ts +14 -0
  67. package/dist/Metal/functions.js +26 -0
  68. package/dist/MetalKit/functions.d.ts +11 -0
  69. package/dist/MetalKit/functions.js +20 -0
  70. package/dist/MetalPerformanceShaders/functions.d.ts +7 -0
  71. package/dist/MetalPerformanceShaders/functions.js +14 -0
  72. package/dist/NearbyInteraction/functions.d.ts +3 -0
  73. package/dist/NearbyInteraction/functions.js +5 -0
  74. package/dist/ParavirtualizedGraphics/functions.d.ts +7 -0
  75. package/dist/ParavirtualizedGraphics/functions.js +14 -0
  76. package/dist/QuartzCore/functions.d.ts +19 -0
  77. package/dist/QuartzCore/functions.js +50 -0
  78. package/dist/SceneKit/functions.d.ts +17 -0
  79. package/dist/SceneKit/functions.js +38 -0
  80. package/dist/SensorKit/functions.d.ts +4 -0
  81. package/dist/SensorKit/functions.js +14 -0
  82. package/dist/ServiceManagement/functions.d.ts +7 -0
  83. package/dist/ServiceManagement/functions.js +20 -0
  84. package/dist/StoreKit/functions.d.ts +1 -0
  85. package/dist/StoreKit/functions.js +5 -0
  86. package/dist/VideoToolbox/functions.d.ts +81 -0
  87. package/dist/VideoToolbox/functions.js +236 -0
  88. package/dist/Vision/functions.d.ts +16 -0
  89. package/dist/Vision/functions.js +38 -0
  90. package/generator/ast-parser.ts +1368 -0
  91. package/generator/clang.ts +167 -0
  92. package/generator/custom.ts +936 -0
  93. package/generator/discover.ts +111 -0
  94. package/generator/emitter.ts +2020 -0
  95. package/generator/frameworks.ts +135 -0
  96. package/generator/index.ts +1334 -0
  97. package/generator/parse-worker.ts +263 -0
  98. package/generator/resolve-strings.ts +121 -0
  99. package/generator/struct-fields.ts +46 -0
  100. package/generator/templates/bind.ts +100 -0
  101. package/generator/templates/helpers.ts +70 -0
  102. package/generator/templates/nsdata.ts +97 -0
  103. package/generator/templates/osversion.ts +91 -0
  104. package/generator/type-mapper.ts +615 -0
  105. package/generator/worker-pool.ts +309 -0
  106. package/package.json +17 -4
@@ -0,0 +1,936 @@
1
+ /**
2
+ * Custom header generator CLI — generates TypeScript type declarations
3
+ * from arbitrary Objective-C header files (not just SDK frameworks).
4
+ *
5
+ * Usage:
6
+ * bun run generate:custom header1.h [header2.h ...] [--include <header>] [-I <path>] [-D <macro[=value]>]
7
+ *
8
+ * Output goes to ./objcjs-types-output/ in the current working directory.
9
+ *
10
+ * Generated files import SDK types from the "objcjs-types" package
11
+ * (e.g., `import type { _NSObject } from "objcjs-types/Foundation"`)
12
+ * and custom types use relative imports within the output directory.
13
+ */
14
+
15
+ import { mkdir, writeFile, rm } from "fs/promises";
16
+ import { existsSync } from "fs";
17
+ import { join, resolve, basename } from "path";
18
+ import { discoverAllFrameworks, type FrameworkConfig } from "./frameworks.ts";
19
+ import { discoverFramework } from "./discover.ts";
20
+ import { clangBatchASTDump, type ClangASTNode } from "./clang.ts";
21
+ import {
22
+ propagateLocFile,
23
+ parseAST,
24
+ parseProtocols,
25
+ parseIntegerEnums,
26
+ parseStringEnums,
27
+ parseStructs,
28
+ parseTypedefs
29
+ } from "./ast-parser.ts";
30
+ import type { ObjCClass, ObjCProtocol, ObjCIntegerEnum, ObjCStringEnum } from "./ast-parser.ts";
31
+ import {
32
+ setKnownClasses,
33
+ setKnownProtocols,
34
+ setProtocolConformers,
35
+ setKnownIntegerEnums,
36
+ setKnownStringEnums,
37
+ setKnownStructs,
38
+ setKnownTypedefs,
39
+ mapReturnType,
40
+ mapParamType,
41
+ selectorToJS,
42
+ STRUCT_TS_TYPES
43
+ } from "./type-mapper.ts";
44
+ import { emitClassBody, sanitizeParamName, emitIntegerEnumFile, emitStringEnumFile } from "./emitter.ts";
45
+
46
+ const AUTOGEN_HEADER = `// AUTO-GENERATED by objcjs-types custom generator — DO NOT EDIT`;
47
+ const OUTPUT_DIR = join(process.cwd(), "objcjs-types-output");
48
+
49
+ // ============================================================================
50
+ // CLI argument parsing
51
+ // ============================================================================
52
+
53
+ interface CLIArgs {
54
+ headerPaths: string[];
55
+ includes: string[];
56
+ includeDirs: string[];
57
+ defines: string[];
58
+ }
59
+
60
+ function parseArgs(): CLIArgs {
61
+ const args = process.argv.slice(2);
62
+ const headerPaths: string[] = [];
63
+ const includes: string[] = [];
64
+ const includeDirs: string[] = [];
65
+ const defines: string[] = [];
66
+
67
+ for (let i = 0; i < args.length; i++) {
68
+ const arg = args[i]!;
69
+ if (arg === "--include" && i + 1 < args.length) {
70
+ includes.push(args[++i]!);
71
+ } else if (arg === "-I" && i + 1 < args.length) {
72
+ includeDirs.push(args[++i]!);
73
+ } else if (arg.startsWith("-I")) {
74
+ // Support -I/path/to/dir (no space)
75
+ includeDirs.push(arg.slice(2));
76
+ } else if (arg === "-D" && i + 1 < args.length) {
77
+ defines.push(args[++i]!);
78
+ } else if (arg.startsWith("-D")) {
79
+ // Support -DMACRO=value (no space)
80
+ defines.push(arg.slice(2));
81
+ } else if (arg.startsWith("-")) {
82
+ console.error(`Unknown flag: ${arg}`);
83
+ console.error(
84
+ "Usage: bun run generate:custom header1.h [header2.h ...] [--include <header>] [-I <path>] [-D <macro[=value]>]"
85
+ );
86
+ process.exit(1);
87
+ } else {
88
+ headerPaths.push(resolve(arg));
89
+ }
90
+ }
91
+
92
+ if (headerPaths.length === 0) {
93
+ console.error("Error: No header files specified.");
94
+ console.error(
95
+ "Usage: bun run generate:custom header1.h [header2.h ...] [--include <header>] [-I <path>] [-D <macro[=value]>]"
96
+ );
97
+ process.exit(1);
98
+ }
99
+
100
+ // Validate that all headers exist
101
+ for (const h of headerPaths) {
102
+ if (!existsSync(h)) {
103
+ console.error(`Error: Header file not found: ${h}`);
104
+ process.exit(1);
105
+ }
106
+ }
107
+
108
+ return { headerPaths, includes, includeDirs, defines };
109
+ }
110
+
111
+ // ============================================================================
112
+ // Discovery helpers — scan custom headers for class/protocol/enum names
113
+ // ============================================================================
114
+
115
+ /** Matches `@interface ClassName` (not categories) */
116
+ const INTERFACE_RE = /@interface\s+(\w+)/;
117
+ const CATEGORY_RE = /@interface\s+\w+(?:<[^>]*>)?\s*\(/;
118
+ const PROTOCOL_RE = /@protocol\s+(\w+)/;
119
+ const NS_ENUM_RE = /typedef\s+NS_(?:ENUM|OPTIONS)\s*\(\s*\w+\s*,\s*(\w+)\s*\)/;
120
+ const NS_STRING_ENUM_RE = /typedef\s+NSString\s*\*\s*(\w+)\s+NS_(?:TYPED_EXTENSIBLE_ENUM|STRING_ENUM|TYPED_ENUM)/;
121
+
122
+ interface CustomDiscovery {
123
+ classes: Set<string>;
124
+ protocols: Set<string>;
125
+ integerEnums: Set<string>;
126
+ stringEnums: Set<string>;
127
+ }
128
+
129
+ /** Scan custom header files for class/protocol/enum declarations. */
130
+ async function discoverCustomHeaders(headerPaths: string[]): Promise<CustomDiscovery> {
131
+ const classes = new Set<string>();
132
+ const protocols = new Set<string>();
133
+ const integerEnums = new Set<string>();
134
+ const stringEnums = new Set<string>();
135
+
136
+ for (const headerPath of headerPaths) {
137
+ const content = await Bun.file(headerPath).text();
138
+ const lines = content.split("\n");
139
+
140
+ for (const line of lines) {
141
+ const ifaceMatch = INTERFACE_RE.exec(line);
142
+ if (ifaceMatch && !CATEGORY_RE.test(line)) {
143
+ classes.add(ifaceMatch[1]!);
144
+ }
145
+
146
+ const protoMatch = PROTOCOL_RE.exec(line);
147
+ if (protoMatch && !line.includes(";")) {
148
+ protocols.add(protoMatch[1]!);
149
+ }
150
+
151
+ const enumMatch = NS_ENUM_RE.exec(line);
152
+ if (enumMatch) {
153
+ integerEnums.add(enumMatch[1]!);
154
+ }
155
+
156
+ const stringEnumMatch = NS_STRING_ENUM_RE.exec(line);
157
+ if (stringEnumMatch) {
158
+ stringEnums.add(stringEnumMatch[1]!);
159
+ }
160
+ }
161
+ }
162
+
163
+ return { classes, protocols, integerEnums, stringEnums };
164
+ }
165
+
166
+ // ============================================================================
167
+ // Custom emit functions — generate .ts files with custom import resolution
168
+ // ============================================================================
169
+
170
+ /**
171
+ * Resolve a type reference to its import source.
172
+ * SDK types import from "objcjs-types/<Framework>", custom types use relative "./<Name>.js".
173
+ */
174
+ function resolveImportPath(
175
+ name: string,
176
+ sdkClassFrameworkMap: Map<string, string>,
177
+ sdkProtocolFrameworkMap: Map<string, string>,
178
+ sdkEnumFrameworkMap: Map<string, string>,
179
+ customNames: Set<string>
180
+ ): { path: string; isCustom: boolean } | null {
181
+ if (customNames.has(name)) {
182
+ return { path: `./${name}.js`, isCustom: true };
183
+ }
184
+ const classFw = sdkClassFrameworkMap.get(name);
185
+ if (classFw) {
186
+ return { path: `objcjs-types/${classFw}`, isCustom: false };
187
+ }
188
+ const protoFw = sdkProtocolFrameworkMap.get(name);
189
+ if (protoFw) {
190
+ return { path: `objcjs-types/${protoFw}`, isCustom: false };
191
+ }
192
+ const enumFw = sdkEnumFrameworkMap.get(name);
193
+ if (enumFw) {
194
+ return { path: `objcjs-types/${enumFw}`, isCustom: false };
195
+ }
196
+ return null;
197
+ }
198
+
199
+ /**
200
+ * Extract all _ClassName references from a mapped type string.
201
+ */
202
+ function extractClassRefs(typeStr: string, selfName: string, allKnown: Set<string>): Set<string> {
203
+ const refs = new Set<string>();
204
+ const regex = /_(\w+)/g;
205
+ let match;
206
+ while ((match = regex.exec(typeStr)) !== null) {
207
+ const name = match[1]!;
208
+ if (name === selfName) continue;
209
+ if (allKnown.has(name)) {
210
+ refs.add(name);
211
+ }
212
+ }
213
+ return refs;
214
+ }
215
+
216
+ /**
217
+ * Collect all class/protocol names referenced by a class's methods and properties.
218
+ */
219
+ function collectClassRefs(cls: ObjCClass, allKnownClasses: Set<string>, allKnownProtocols: Set<string>): Set<string> {
220
+ const allKnown = new Set([...allKnownClasses, ...allKnownProtocols]);
221
+ const refs = new Set<string>();
222
+
223
+ // Superclass
224
+ if (cls.superclass && allKnown.has(cls.superclass)) {
225
+ refs.add(cls.superclass);
226
+ }
227
+
228
+ // Conformed protocols
229
+ for (const protoName of cls.protocols) {
230
+ if (allKnownProtocols.has(protoName)) {
231
+ refs.add(protoName);
232
+ }
233
+ }
234
+
235
+ // Method signatures
236
+ const allMethods = [...cls.instanceMethods, ...cls.classMethods];
237
+ for (const method of allMethods) {
238
+ for (const ref of extractClassRefs(mapReturnType(method.returnType, cls.name), cls.name, allKnown)) {
239
+ refs.add(ref);
240
+ }
241
+ for (const param of method.parameters) {
242
+ for (const ref of extractClassRefs(mapParamType(param.type, cls.name), cls.name, allKnown)) {
243
+ refs.add(ref);
244
+ }
245
+ }
246
+ }
247
+
248
+ // Properties
249
+ for (const prop of cls.properties) {
250
+ for (const ref of extractClassRefs(mapReturnType(prop.type, cls.name), cls.name, allKnown)) {
251
+ refs.add(ref);
252
+ }
253
+ for (const ref of extractClassRefs(mapParamType(prop.type, cls.name), cls.name, allKnown)) {
254
+ refs.add(ref);
255
+ }
256
+ }
257
+
258
+ refs.delete(cls.name);
259
+ return refs;
260
+ }
261
+
262
+ /**
263
+ * Collect all struct type names referenced by a class.
264
+ */
265
+ function collectStructRefs(cls: ObjCClass): Set<string> {
266
+ const refs = new Set<string>();
267
+ function extract(typeStr: string): void {
268
+ for (const structName of STRUCT_TS_TYPES) {
269
+ if (typeStr === structName || typeStr.startsWith(structName + " ")) {
270
+ refs.add(structName);
271
+ }
272
+ }
273
+ }
274
+ for (const method of [...cls.instanceMethods, ...cls.classMethods]) {
275
+ extract(mapReturnType(method.returnType, cls.name));
276
+ for (const param of method.parameters) {
277
+ extract(mapParamType(param.type, cls.name));
278
+ }
279
+ }
280
+ for (const prop of cls.properties) {
281
+ extract(mapReturnType(prop.type, cls.name));
282
+ extract(mapParamType(prop.type, cls.name));
283
+ }
284
+ return refs;
285
+ }
286
+
287
+ /**
288
+ * Collect all enum type names referenced by a class.
289
+ */
290
+ function collectEnumRefs(
291
+ cls: ObjCClass,
292
+ allKnownIntegerEnums: Set<string>,
293
+ allKnownStringEnums: Set<string>
294
+ ): Set<string> {
295
+ const refs = new Set<string>();
296
+ function extract(typeStr: string): void {
297
+ for (const part of typeStr.split(" | ")) {
298
+ const trimmed = part.trim();
299
+ if (allKnownIntegerEnums.has(trimmed) || allKnownStringEnums.has(trimmed)) {
300
+ refs.add(trimmed);
301
+ }
302
+ }
303
+ }
304
+ for (const method of [...cls.instanceMethods, ...cls.classMethods]) {
305
+ extract(mapReturnType(method.returnType, cls.name));
306
+ for (const param of method.parameters) {
307
+ extract(mapParamType(param.type, cls.name));
308
+ }
309
+ }
310
+ for (const prop of cls.properties) {
311
+ extract(mapReturnType(prop.type, cls.name));
312
+ extract(mapParamType(prop.type, cls.name));
313
+ }
314
+ return refs;
315
+ }
316
+
317
+ /**
318
+ * Collect all class/protocol names referenced by a protocol's methods and properties.
319
+ */
320
+ function collectProtocolClassRefs(
321
+ proto: ObjCProtocol,
322
+ allKnownClasses: Set<string>,
323
+ allKnownProtocols: Set<string>
324
+ ): Set<string> {
325
+ const allKnown = new Set([...allKnownClasses, ...allKnownProtocols]);
326
+ const refs = new Set<string>();
327
+
328
+ // Extended protocols
329
+ for (const ext of proto.extendedProtocols) {
330
+ if (allKnown.has(ext)) {
331
+ refs.add(ext);
332
+ }
333
+ }
334
+
335
+ // Method signatures
336
+ for (const method of [...proto.instanceMethods, ...proto.classMethods]) {
337
+ for (const ref of extractClassRefs(mapReturnType(method.returnType, proto.name), proto.name, allKnown)) {
338
+ refs.add(ref);
339
+ }
340
+ for (const param of method.parameters) {
341
+ for (const ref of extractClassRefs(mapParamType(param.type, proto.name), proto.name, allKnown)) {
342
+ refs.add(ref);
343
+ }
344
+ }
345
+ }
346
+
347
+ for (const prop of proto.properties) {
348
+ for (const ref of extractClassRefs(mapReturnType(prop.type, proto.name), proto.name, allKnown)) {
349
+ refs.add(ref);
350
+ }
351
+ for (const ref of extractClassRefs(mapParamType(prop.type, proto.name), proto.name, allKnown)) {
352
+ refs.add(ref);
353
+ }
354
+ }
355
+
356
+ refs.delete(proto.name);
357
+ return refs;
358
+ }
359
+
360
+ /**
361
+ * Collect struct refs from a protocol.
362
+ */
363
+ function collectProtocolStructRefs(proto: ObjCProtocol): Set<string> {
364
+ const refs = new Set<string>();
365
+ function extract(typeStr: string): void {
366
+ for (const structName of STRUCT_TS_TYPES) {
367
+ if (typeStr === structName || typeStr.startsWith(structName + " ")) {
368
+ refs.add(structName);
369
+ }
370
+ }
371
+ }
372
+ for (const method of [...proto.instanceMethods, ...proto.classMethods]) {
373
+ extract(mapReturnType(method.returnType, proto.name));
374
+ for (const param of method.parameters) {
375
+ extract(mapParamType(param.type, proto.name));
376
+ }
377
+ }
378
+ for (const prop of proto.properties) {
379
+ extract(mapReturnType(prop.type, proto.name));
380
+ extract(mapParamType(prop.type, proto.name));
381
+ }
382
+ return refs;
383
+ }
384
+
385
+ /**
386
+ * Collect enum refs from a protocol.
387
+ */
388
+ function collectProtocolEnumRefs(
389
+ proto: ObjCProtocol,
390
+ allKnownIntegerEnums: Set<string>,
391
+ allKnownStringEnums: Set<string>
392
+ ): Set<string> {
393
+ const refs = new Set<string>();
394
+ function extract(typeStr: string): void {
395
+ for (const part of typeStr.split(" | ")) {
396
+ const trimmed = part.trim();
397
+ if (allKnownIntegerEnums.has(trimmed) || allKnownStringEnums.has(trimmed)) {
398
+ refs.add(trimmed);
399
+ }
400
+ }
401
+ }
402
+ for (const method of [...proto.instanceMethods, ...proto.classMethods]) {
403
+ extract(mapReturnType(method.returnType, proto.name));
404
+ for (const param of method.parameters) {
405
+ extract(mapParamType(param.type, proto.name));
406
+ }
407
+ }
408
+ for (const prop of proto.properties) {
409
+ extract(mapReturnType(prop.type, proto.name));
410
+ extract(mapParamType(prop.type, proto.name));
411
+ }
412
+ return refs;
413
+ }
414
+
415
+ /**
416
+ * Emit imports for a set of referenced names, routing to SDK packages or relative paths.
417
+ */
418
+ function emitCustomImports(
419
+ refs: Set<string>,
420
+ sdkClassFrameworkMap: Map<string, string>,
421
+ sdkProtocolFrameworkMap: Map<string, string>,
422
+ sdkEnumFrameworkMap: Map<string, string>,
423
+ customNames: Set<string>
424
+ ): string[] {
425
+ const lines: string[] = [];
426
+
427
+ // Group by import path
428
+ const byPath = new Map<string, string[]>();
429
+ for (const name of [...refs].sort()) {
430
+ const resolved = resolveImportPath(
431
+ name,
432
+ sdkClassFrameworkMap,
433
+ sdkProtocolFrameworkMap,
434
+ sdkEnumFrameworkMap,
435
+ customNames
436
+ );
437
+ if (!resolved) continue;
438
+ if (!byPath.has(resolved.path)) {
439
+ byPath.set(resolved.path, []);
440
+ }
441
+ byPath.get(resolved.path)!.push(name);
442
+ }
443
+
444
+ for (const [path, names] of byPath) {
445
+ const specifiers = names.map((n) => `_${n}`).join(", ");
446
+ lines.push(`import type { ${specifiers} } from "${path}";`);
447
+ }
448
+
449
+ return lines;
450
+ }
451
+
452
+ /**
453
+ * Emit struct imports, routing to SDK package or skipping (custom headers
454
+ * generally don't define structs — they come from SDK pre-includes).
455
+ */
456
+ function emitCustomStructImports(refs: Set<string>): string[] {
457
+ const lines: string[] = [];
458
+ for (const name of [...refs].sort()) {
459
+ // Structs are exported from objcjs-types top-level
460
+ lines.push(`import type { ${name} } from "objcjs-types";`);
461
+ }
462
+ return lines;
463
+ }
464
+
465
+ /**
466
+ * Emit enum imports, routing to the correct SDK framework package.
467
+ */
468
+ function emitCustomEnumImports(
469
+ refs: Set<string>,
470
+ sdkEnumFrameworkMap: Map<string, string>,
471
+ customEnumNames: Set<string>
472
+ ): string[] {
473
+ const lines: string[] = [];
474
+ for (const name of [...refs].sort()) {
475
+ if (customEnumNames.has(name)) {
476
+ lines.push(`import type { ${name} } from "./${name}.js";`);
477
+ } else {
478
+ const fw = sdkEnumFrameworkMap.get(name);
479
+ if (fw) {
480
+ lines.push(`import type { ${name} } from "objcjs-types/${fw}";`);
481
+ }
482
+ }
483
+ }
484
+ return lines;
485
+ }
486
+
487
+ /**
488
+ * Emit a complete .ts file for a custom class.
489
+ */
490
+ function emitCustomClassFile(
491
+ cls: ObjCClass,
492
+ allKnownClasses: Set<string>,
493
+ allKnownProtocols: Set<string>,
494
+ allKnownIntegerEnums: Set<string>,
495
+ allKnownStringEnums: Set<string>,
496
+ sdkClassFrameworkMap: Map<string, string>,
497
+ sdkProtocolFrameworkMap: Map<string, string>,
498
+ sdkEnumFrameworkMap: Map<string, string>,
499
+ customNames: Set<string>,
500
+ customEnumNames: Set<string>,
501
+ allParsedClasses?: Map<string, ObjCClass>
502
+ ): string {
503
+ const lines: string[] = [];
504
+ lines.push(AUTOGEN_HEADER);
505
+ lines.push(`import type { NobjcObject } from "objc-js";`);
506
+
507
+ // Class/protocol imports
508
+ const classRefs = collectClassRefs(cls, allKnownClasses, allKnownProtocols);
509
+ lines.push(
510
+ ...emitCustomImports(classRefs, sdkClassFrameworkMap, sdkProtocolFrameworkMap, sdkEnumFrameworkMap, customNames)
511
+ );
512
+
513
+ // Struct imports
514
+ const structRefs = collectStructRefs(cls);
515
+ lines.push(...emitCustomStructImports(structRefs));
516
+
517
+ // Enum imports
518
+ const enumRefs = collectEnumRefs(cls, allKnownIntegerEnums, allKnownStringEnums);
519
+ lines.push(...emitCustomEnumImports(enumRefs, sdkEnumFrameworkMap, customEnumNames));
520
+
521
+ lines.push("");
522
+ lines.push(...emitClassBody(cls, allKnownClasses, allParsedClasses));
523
+
524
+ // Protocol conformance via declaration merging (simplified — no conflict detection
525
+ // since we don't have full SDK parsed class data for ancestor signature analysis)
526
+ if (cls.protocols.length > 0) {
527
+ const knownProtos = cls.protocols.filter((p) => allKnownProtocols.has(p));
528
+ if (knownProtos.length > 0) {
529
+ const extendsList = knownProtos.map((p) => `_${p}`).join(", ");
530
+ lines.push(`export interface _${cls.name} extends ${extendsList} {}`);
531
+ }
532
+ }
533
+
534
+ lines.push("");
535
+ return lines.join("\n");
536
+ }
537
+
538
+ /**
539
+ * Emit a complete .ts file for a custom protocol.
540
+ */
541
+ function emitCustomProtocolFile(
542
+ proto: ObjCProtocol,
543
+ allKnownClasses: Set<string>,
544
+ allKnownProtocols: Set<string>,
545
+ allKnownIntegerEnums: Set<string>,
546
+ allKnownStringEnums: Set<string>,
547
+ sdkClassFrameworkMap: Map<string, string>,
548
+ sdkProtocolFrameworkMap: Map<string, string>,
549
+ sdkEnumFrameworkMap: Map<string, string>,
550
+ customNames: Set<string>,
551
+ customEnumNames: Set<string>
552
+ ): string {
553
+ const lines: string[] = [];
554
+ lines.push(AUTOGEN_HEADER);
555
+ lines.push(`import type { NobjcObject } from "objc-js";`);
556
+
557
+ // Class/protocol imports
558
+ const classRefs = collectProtocolClassRefs(proto, allKnownClasses, allKnownProtocols);
559
+ lines.push(
560
+ ...emitCustomImports(classRefs, sdkClassFrameworkMap, sdkProtocolFrameworkMap, sdkEnumFrameworkMap, customNames)
561
+ );
562
+
563
+ // Struct imports
564
+ const structRefs = collectProtocolStructRefs(proto);
565
+ lines.push(...emitCustomStructImports(structRefs));
566
+
567
+ // Enum imports
568
+ const enumRefs = collectProtocolEnumRefs(proto, allKnownIntegerEnums, allKnownStringEnums);
569
+ lines.push(...emitCustomEnumImports(enumRefs, sdkEnumFrameworkMap, customEnumNames));
570
+
571
+ lines.push("");
572
+
573
+ // Extends clause
574
+ const extendsClauses: string[] = [];
575
+ for (const ext of proto.extendedProtocols) {
576
+ if (allKnownProtocols.has(ext)) {
577
+ extendsClauses.push(`_${ext}`);
578
+ }
579
+ }
580
+ const extendsStr = extendsClauses.length > 0 ? ` extends ${extendsClauses.join(", ")}` : "";
581
+
582
+ lines.push(`export interface _${proto.name}${extendsStr} {`);
583
+
584
+ // Instance properties
585
+ const instanceProps = proto.properties.filter((p) => !p.isClassProperty);
586
+ const instancePropertyNames = new Set(instanceProps.map((p) => p.name));
587
+ const regularMethods = proto.instanceMethods.filter((m) => !instancePropertyNames.has(m.selector));
588
+
589
+ // Instance methods (optional)
590
+ if (regularMethods.length > 0) {
591
+ lines.push(" // Instance methods");
592
+ for (const method of regularMethods) {
593
+ const jsName = selectorToJS(method.selector);
594
+ const returnType = mapReturnType(method.returnType, proto.name);
595
+ const params: string[] = [];
596
+ const seenNames = new Map<string, number>();
597
+ for (const param of method.parameters) {
598
+ const tsType = mapParamType(param.type, proto.name);
599
+ let safeName = sanitizeParamName(param.name);
600
+ const prev = seenNames.get(safeName) ?? 0;
601
+ seenNames.set(safeName, prev + 1);
602
+ if (prev > 0) {
603
+ safeName = `${safeName}${prev + 1}`;
604
+ }
605
+ params.push(`${safeName}: ${tsType}`);
606
+ }
607
+ lines.push(` ${jsName}?(${params.join(", ")}): ${returnType};`);
608
+ }
609
+ }
610
+
611
+ // Instance properties (optional getters)
612
+ if (instanceProps.length > 0) {
613
+ if (regularMethods.length > 0) lines.push("");
614
+ lines.push(" // Properties");
615
+ for (const prop of instanceProps) {
616
+ const tsType = mapReturnType(prop.type, proto.name);
617
+ lines.push(` ${prop.name}?(): ${tsType};`);
618
+ if (!prop.readonly) {
619
+ const setterName = `set${prop.name[0]!.toUpperCase()}${prop.name.slice(1)}$`;
620
+ const paramType = mapParamType(prop.type, proto.name);
621
+ lines.push(` ${setterName}?(value: ${paramType}): void;`);
622
+ }
623
+ }
624
+ }
625
+
626
+ lines.push("}");
627
+ lines.push("");
628
+ return lines.join("\n");
629
+ }
630
+
631
+ /**
632
+ * Emit the barrel index.ts for the custom output directory.
633
+ */
634
+ function emitCustomIndex(
635
+ classNames: string[],
636
+ protocolNames: string[],
637
+ integerEnumNames: string[],
638
+ stringEnumNames: string[]
639
+ ): string {
640
+ const lines: string[] = [];
641
+ lines.push(AUTOGEN_HEADER);
642
+ lines.push("");
643
+
644
+ for (const name of classNames.sort()) {
645
+ lines.push(`export type { _${name} } from "./${name}.js";`);
646
+ }
647
+ for (const name of protocolNames.sort()) {
648
+ lines.push(`export type { _${name} } from "./${name}.js";`);
649
+ }
650
+ for (const name of integerEnumNames.sort()) {
651
+ lines.push(`export { ${name} } from "./${name}.js";`);
652
+ }
653
+ for (const name of stringEnumNames.sort()) {
654
+ lines.push(`export { ${name} } from "./${name}.js";`);
655
+ }
656
+
657
+ lines.push("");
658
+ return lines.join("\n");
659
+ }
660
+
661
+ // ============================================================================
662
+ // Main pipeline
663
+ // ============================================================================
664
+
665
+ async function main(): Promise<void> {
666
+ console.log("=== objcjs-types custom generator ===\n");
667
+ const globalStart = performance.now();
668
+
669
+ const { headerPaths, includes, includeDirs, defines } = parseArgs();
670
+ console.log(`Input headers: ${headerPaths.map((h) => basename(h)).join(", ")}`);
671
+ if (includes.length > 0) console.log(`Extra includes: ${includes.join(", ")}`);
672
+ if (includeDirs.length > 0) console.log(`Include dirs: ${includeDirs.join(", ")}`);
673
+ if (defines.length > 0) console.log(`Defines: ${defines.join(", ")}`);
674
+ console.log("");
675
+
676
+ // ========================================
677
+ // Phase 1: SDK Discovery (populate type-mapper state)
678
+ // ========================================
679
+ console.log("Phase 1: Discovering SDK frameworks...");
680
+ const allBases = await discoverAllFrameworks();
681
+ console.log(` Found ${allBases.length} frameworks with headers`);
682
+
683
+ const frameworks: FrameworkConfig[] = [];
684
+ const discoveryResults = await Promise.all(
685
+ allBases.map(async (base) => {
686
+ const discovery = await discoverFramework(base.headersPath);
687
+ return { base, discovery };
688
+ })
689
+ );
690
+
691
+ for (const { base, discovery } of discoveryResults) {
692
+ // Filter out protocols whose names clash with class names
693
+ for (const protoName of discovery.protocols.keys()) {
694
+ if (discovery.classes.has(protoName)) {
695
+ discovery.protocols.delete(protoName);
696
+ }
697
+ }
698
+
699
+ // Add extraHeaders classes
700
+ if (base.extraHeaders) {
701
+ for (const className of Object.keys(base.extraHeaders)) {
702
+ if (!discovery.classes.has(className)) {
703
+ discovery.classes.set(className, className);
704
+ }
705
+ }
706
+ }
707
+
708
+ if (
709
+ discovery.classes.size === 0 &&
710
+ discovery.protocols.size === 0 &&
711
+ discovery.integerEnums.size === 0 &&
712
+ discovery.stringEnums.size === 0
713
+ )
714
+ continue;
715
+
716
+ frameworks.push({
717
+ ...base,
718
+ classes: [...discovery.classes.keys()].sort(),
719
+ protocols: [...discovery.protocols.keys()].sort(),
720
+ integerEnums: [...discovery.integerEnums.keys()].sort(),
721
+ stringEnums: [...discovery.stringEnums.keys()].sort(),
722
+ classHeaders: discovery.classes,
723
+ protocolHeaders: discovery.protocols,
724
+ integerEnumHeaders: discovery.integerEnums,
725
+ stringEnumHeaders: discovery.stringEnums
726
+ });
727
+ }
728
+
729
+ // Build SDK lookup maps
730
+ const sdkClassFrameworkMap = new Map<string, string>();
731
+ const sdkProtocolFrameworkMap = new Map<string, string>();
732
+ const sdkEnumFrameworkMap = new Map<string, string>();
733
+
734
+ const allKnownClasses = new Set<string>();
735
+ const allKnownProtocols = new Set<string>();
736
+ const allKnownIntegerEnums = new Set<string>();
737
+ const allKnownStringEnums = new Set<string>();
738
+
739
+ for (const fw of frameworks) {
740
+ for (const cls of fw.classes) {
741
+ allKnownClasses.add(cls);
742
+ sdkClassFrameworkMap.set(cls, fw.name);
743
+ }
744
+ for (const proto of fw.protocols) {
745
+ allKnownProtocols.add(proto);
746
+ sdkProtocolFrameworkMap.set(proto, fw.name);
747
+ }
748
+ for (const name of fw.integerEnums) {
749
+ allKnownIntegerEnums.add(name);
750
+ sdkEnumFrameworkMap.set(name, fw.name);
751
+ }
752
+ for (const name of fw.stringEnums) {
753
+ allKnownStringEnums.add(name);
754
+ sdkEnumFrameworkMap.set(name, fw.name);
755
+ }
756
+ }
757
+
758
+ const discoveryTime = ((performance.now() - globalStart) / 1000).toFixed(1);
759
+ console.log(` SDK discovery completed in ${discoveryTime}s\n`);
760
+
761
+ // ========================================
762
+ // Phase 2: Discover custom headers
763
+ // ========================================
764
+ console.log("Phase 2: Scanning custom headers...");
765
+ const customDiscovery = await discoverCustomHeaders(headerPaths);
766
+ console.log(
767
+ ` Found ${customDiscovery.classes.size} classes, ${customDiscovery.protocols.size} protocols, ` +
768
+ `${customDiscovery.integerEnums.size + customDiscovery.stringEnums.size} enums`
769
+ );
770
+
771
+ if (
772
+ customDiscovery.classes.size === 0 &&
773
+ customDiscovery.protocols.size === 0 &&
774
+ customDiscovery.integerEnums.size === 0 &&
775
+ customDiscovery.stringEnums.size === 0
776
+ ) {
777
+ console.error("\nNo ObjC classes, protocols, or enums found in the provided headers.");
778
+ process.exit(1);
779
+ }
780
+
781
+ // Add custom names to the global known sets so the type-mapper resolves them
782
+ const customNames = new Set<string>([...customDiscovery.classes, ...customDiscovery.protocols]);
783
+ const customEnumNames = new Set<string>([...customDiscovery.integerEnums, ...customDiscovery.stringEnums]);
784
+
785
+ for (const name of customDiscovery.classes) allKnownClasses.add(name);
786
+ for (const name of customDiscovery.protocols) allKnownProtocols.add(name);
787
+ for (const name of customDiscovery.integerEnums) allKnownIntegerEnums.add(name);
788
+ for (const name of customDiscovery.stringEnums) allKnownStringEnums.add(name);
789
+
790
+ // Set global type-mapper state
791
+ setKnownClasses(allKnownClasses);
792
+ setKnownProtocols(allKnownProtocols);
793
+ setKnownIntegerEnums(allKnownIntegerEnums);
794
+ setKnownStringEnums(allKnownStringEnums);
795
+ // Empty conformers — we don't need return type union expansion for custom headers
796
+ setProtocolConformers(new Map());
797
+
798
+ // ========================================
799
+ // Phase 3: Clang AST parsing
800
+ // ========================================
801
+ console.log("\nPhase 3: Parsing custom headers via clang...");
802
+
803
+ // Build extra clang args for -I include dirs, --include headers, and -D defines
804
+ const extraArgs: string[] = [];
805
+ for (const dir of includeDirs) {
806
+ extraArgs.push("-I", resolve(dir));
807
+ }
808
+ for (const inc of includes) {
809
+ extraArgs.push("-include", inc);
810
+ }
811
+ for (const def of defines) {
812
+ extraArgs.push(`-D${def}`);
813
+ }
814
+
815
+ const preIncludes = ["Foundation/Foundation.h"];
816
+ const parseStart = performance.now();
817
+ const ast = await clangBatchASTDump(headerPaths, preIncludes, extraArgs.length > 0 ? extraArgs : undefined);
818
+
819
+ // Propagate source file locations through the AST
820
+ propagateLocFile(ast);
821
+
822
+ // Parse structs and typedefs from the AST (these include SDK types via Foundation pre-include)
823
+ const { structs: parsedStructs, aliases: parsedAliases } = parseStructs(ast);
824
+ const parsedTypedefs = parseTypedefs(ast);
825
+
826
+ // Register struct/typedef state so type-mapper can resolve struct types
827
+ const structNames = new Set(parsedStructs.keys());
828
+ const aliasMap = new Map(parsedAliases.map((a) => [a.name, a.target]));
829
+ const internalNameMap = new Map<string, string>();
830
+ for (const [name, structDef] of parsedStructs) {
831
+ if (structDef.internalName) {
832
+ internalNameMap.set(name, structDef.internalName);
833
+ }
834
+ }
835
+ setKnownStructs(structNames, aliasMap, internalNameMap);
836
+ setKnownTypedefs(parsedTypedefs);
837
+
838
+ // Parse classes, protocols, and enums from the custom headers
839
+ const parsedClasses = parseAST(ast, customDiscovery.classes);
840
+ const parsedProtocols = parseProtocols(ast, customDiscovery.protocols);
841
+ const parsedIntegerEnums = parseIntegerEnums(ast, customDiscovery.integerEnums);
842
+ const parsedStringEnums = parseStringEnums(ast, customDiscovery.stringEnums);
843
+
844
+ const parseTime = ((performance.now() - parseStart) / 1000).toFixed(1);
845
+ console.log(
846
+ ` Parsed ${parsedClasses.size} classes, ${parsedProtocols.size} protocols, ` +
847
+ `${parsedIntegerEnums.size + parsedStringEnums.size} enums in ${parseTime}s`
848
+ );
849
+
850
+ // ========================================
851
+ // Phase 4: Emit .ts files
852
+ // ========================================
853
+ console.log("\nPhase 4: Emitting TypeScript declarations...");
854
+
855
+ // Clean and create output directory
856
+ if (existsSync(OUTPUT_DIR)) {
857
+ await rm(OUTPUT_DIR, { recursive: true });
858
+ }
859
+ await mkdir(OUTPUT_DIR, { recursive: true });
860
+
861
+ let filesWritten = 0;
862
+
863
+ // Emit class files
864
+ for (const [name, cls] of parsedClasses) {
865
+ const content = emitCustomClassFile(
866
+ cls,
867
+ allKnownClasses,
868
+ allKnownProtocols,
869
+ allKnownIntegerEnums,
870
+ allKnownStringEnums,
871
+ sdkClassFrameworkMap,
872
+ sdkProtocolFrameworkMap,
873
+ sdkEnumFrameworkMap,
874
+ customNames,
875
+ customEnumNames,
876
+ parsedClasses
877
+ );
878
+ await writeFile(join(OUTPUT_DIR, `${name}.ts`), content);
879
+ filesWritten++;
880
+ console.log(` ${name}.ts (class)`);
881
+ }
882
+
883
+ // Emit protocol files
884
+ for (const [name, proto] of parsedProtocols) {
885
+ const content = emitCustomProtocolFile(
886
+ proto,
887
+ allKnownClasses,
888
+ allKnownProtocols,
889
+ allKnownIntegerEnums,
890
+ allKnownStringEnums,
891
+ sdkClassFrameworkMap,
892
+ sdkProtocolFrameworkMap,
893
+ sdkEnumFrameworkMap,
894
+ customNames,
895
+ customEnumNames
896
+ );
897
+ await writeFile(join(OUTPUT_DIR, `${name}.ts`), content);
898
+ filesWritten++;
899
+ console.log(` ${name}.ts (protocol)`);
900
+ }
901
+
902
+ // Emit integer enum files
903
+ for (const [name, enumDef] of parsedIntegerEnums) {
904
+ const content = emitIntegerEnumFile(enumDef);
905
+ await writeFile(join(OUTPUT_DIR, `${name}.ts`), content);
906
+ filesWritten++;
907
+ console.log(` ${name}.ts (integer enum)`);
908
+ }
909
+
910
+ // Emit string enum files
911
+ for (const [name, enumDef] of parsedStringEnums) {
912
+ const content = emitStringEnumFile(enumDef, "custom");
913
+ await writeFile(join(OUTPUT_DIR, `${name}.ts`), content);
914
+ filesWritten++;
915
+ console.log(` ${name}.ts (string enum)`);
916
+ }
917
+
918
+ // Emit barrel index
919
+ const indexContent = emitCustomIndex(
920
+ [...parsedClasses.keys()],
921
+ [...parsedProtocols.keys()],
922
+ [...parsedIntegerEnums.keys()],
923
+ [...parsedStringEnums.keys()]
924
+ );
925
+ await writeFile(join(OUTPUT_DIR, "index.ts"), indexContent);
926
+ filesWritten++;
927
+ console.log(` index.ts (barrel)`);
928
+
929
+ const totalTime = ((performance.now() - globalStart) / 1000).toFixed(1);
930
+ console.log(`\nDone! Wrote ${filesWritten} files to ${OUTPUT_DIR} in ${totalTime}s`);
931
+ }
932
+
933
+ main().catch((err) => {
934
+ console.error("Fatal error:", err);
935
+ process.exit(1);
936
+ });