objcjs-types 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/objcjs-types.ts +31 -0
- package/dist/AVFoundation/functions.d.ts +21 -0
- package/dist/AVFoundation/functions.js +32 -0
- package/dist/AVFoundation/index.d.ts +1 -0
- package/dist/AVFoundation/index.js +1 -0
- package/dist/Accessibility/functions.d.ts +16 -0
- package/dist/Accessibility/functions.js +35 -0
- package/dist/Accessibility/index.d.ts +1 -0
- package/dist/Accessibility/index.js +1 -0
- package/dist/AddressBook/functions.d.ts +98 -0
- package/dist/AddressBook/functions.js +290 -0
- package/dist/AddressBook/index.d.ts +1 -0
- package/dist/AddressBook/index.js +1 -0
- package/dist/AppKit/functions.d.ts +112 -0
- package/dist/AppKit/functions.js +272 -0
- package/dist/AppKit/index.d.ts +1 -0
- package/dist/AppKit/index.js +1 -0
- package/dist/AudioToolbox/functions.d.ts +377 -0
- package/dist/AudioToolbox/functions.js +1124 -0
- package/dist/AudioToolbox/index.d.ts +1 -0
- package/dist/AudioToolbox/index.js +1 -0
- package/dist/AuthenticationServices/functions.d.ts +2 -0
- package/dist/AuthenticationServices/functions.js +5 -0
- package/dist/AuthenticationServices/index.d.ts +1 -0
- package/dist/AuthenticationServices/index.js +1 -0
- package/dist/BrowserEngineCore/functions.d.ts +3 -0
- package/dist/BrowserEngineCore/functions.js +11 -0
- package/dist/BrowserEngineCore/index.d.ts +1 -0
- package/dist/BrowserEngineCore/index.js +1 -0
- package/dist/CoreAudio/functions.d.ts +60 -0
- package/dist/CoreAudio/functions.js +173 -0
- package/dist/CoreAudio/index.d.ts +1 -0
- package/dist/CoreAudio/index.js +1 -0
- package/dist/CoreMIDI/functions.d.ts +96 -0
- package/dist/CoreMIDI/functions.js +287 -0
- package/dist/CoreMIDI/index.d.ts +1 -0
- package/dist/CoreMIDI/index.js +1 -0
- package/dist/CoreML/functions.d.ts +2 -0
- package/dist/CoreML/functions.js +5 -0
- package/dist/CoreML/index.d.ts +1 -0
- package/dist/CoreML/index.js +1 -0
- package/dist/CoreMediaIO/functions.d.ts +38 -0
- package/dist/CoreMediaIO/functions.js +107 -0
- package/dist/CoreMediaIO/index.d.ts +1 -0
- package/dist/CoreMediaIO/index.js +1 -0
- package/dist/CoreText/functions.d.ts +209 -0
- package/dist/CoreText/functions.js +611 -0
- package/dist/CoreText/index.d.ts +1 -0
- package/dist/CoreText/index.js +1 -0
- package/dist/CoreWLAN/functions.d.ts +23 -0
- package/dist/CoreWLAN/functions.js +56 -0
- package/dist/CoreWLAN/index.d.ts +1 -0
- package/dist/CoreWLAN/index.js +1 -0
- package/dist/DeviceDiscoveryExtension/functions.d.ts +11 -0
- package/dist/DeviceDiscoveryExtension/functions.js +17 -0
- package/dist/DeviceDiscoveryExtension/index.d.ts +1 -0
- package/dist/DeviceDiscoveryExtension/index.js +1 -0
- package/dist/DiscRecording/functions.d.ts +97 -0
- package/dist/DiscRecording/functions.js +290 -0
- package/dist/DiscRecording/index.d.ts +1 -0
- package/dist/DiscRecording/index.js +1 -0
- package/dist/DiscRecordingUI/functions.d.ts +13 -0
- package/dist/DiscRecordingUI/functions.js +38 -0
- package/dist/DiscRecordingUI/index.d.ts +1 -0
- package/dist/DiscRecordingUI/index.js +1 -0
- package/dist/ExceptionHandling/functions.d.ts +1 -0
- package/dist/ExceptionHandling/functions.js +5 -0
- package/dist/ExceptionHandling/index.d.ts +1 -0
- package/dist/ExceptionHandling/index.js +1 -0
- package/dist/FSKit/functions.d.ts +4 -0
- package/dist/FSKit/functions.js +11 -0
- package/dist/FSKit/index.d.ts +1 -0
- package/dist/FSKit/index.js +1 -0
- package/dist/Foundation/functions.d.ts +145 -0
- package/dist/Foundation/functions.js +386 -0
- package/dist/Foundation/index.d.ts +1 -0
- package/dist/Foundation/index.js +1 -0
- package/dist/GLKit/functions.d.ts +51 -0
- package/dist/GLKit/functions.js +146 -0
- package/dist/GLKit/index.d.ts +1 -0
- package/dist/GLKit/index.js +1 -0
- package/dist/GameController/functions.d.ts +18 -0
- package/dist/GameController/functions.js +44 -0
- package/dist/GameController/index.d.ts +1 -0
- package/dist/GameController/index.js +1 -0
- package/dist/HealthKit/functions.d.ts +19 -0
- package/dist/HealthKit/functions.js +35 -0
- package/dist/HealthKit/index.d.ts +1 -0
- package/dist/HealthKit/index.js +1 -0
- package/dist/IOSurface/functions.d.ts +53 -0
- package/dist/IOSurface/functions.js +155 -0
- package/dist/IOSurface/index.d.ts +1 -0
- package/dist/IOSurface/index.js +1 -0
- package/dist/IOUSBHost/functions.d.ts +44 -0
- package/dist/IOUSBHost/functions.js +131 -0
- package/dist/IOUSBHost/index.d.ts +1 -0
- package/dist/IOUSBHost/index.js +1 -0
- package/dist/InstantMessage/functions.d.ts +1 -0
- package/dist/InstantMessage/functions.js +5 -0
- package/dist/InstantMessage/index.d.ts +1 -0
- package/dist/InstantMessage/index.js +1 -0
- package/dist/JavaRuntimeSupport/functions.d.ts +40 -0
- package/dist/JavaRuntimeSupport/functions.js +113 -0
- package/dist/JavaRuntimeSupport/index.d.ts +1 -0
- package/dist/JavaRuntimeSupport/index.js +1 -0
- package/dist/JavaScriptCore/functions.d.ts +120 -0
- package/dist/JavaScriptCore/functions.js +359 -0
- package/dist/JavaScriptCore/index.d.ts +1 -0
- package/dist/JavaScriptCore/index.js +1 -0
- package/dist/MLCompute/functions.d.ts +27 -0
- package/dist/MLCompute/functions.js +41 -0
- package/dist/MLCompute/index.d.ts +1 -0
- package/dist/MLCompute/index.js +1 -0
- package/dist/MapKit/functions.d.ts +23 -0
- package/dist/MapKit/functions.js +56 -0
- package/dist/MapKit/index.d.ts +1 -0
- package/dist/MapKit/index.js +1 -0
- package/dist/Matter/functions.d.ts +17 -0
- package/dist/Matter/functions.js +26 -0
- package/dist/Matter/index.d.ts +1 -0
- package/dist/Matter/index.js +1 -0
- package/dist/MediaAccessibility/functions.d.ts +28 -0
- package/dist/MediaAccessibility/functions.js +83 -0
- package/dist/MediaAccessibility/index.d.ts +1 -0
- package/dist/MediaAccessibility/index.js +1 -0
- package/dist/MediaPlayer/functions.d.ts +3 -0
- package/dist/MediaPlayer/functions.js +11 -0
- package/dist/MediaPlayer/index.d.ts +1 -0
- package/dist/MediaPlayer/index.js +1 -0
- package/dist/Metal/functions.d.ts +14 -0
- package/dist/Metal/functions.js +26 -0
- package/dist/Metal/index.d.ts +1 -0
- package/dist/Metal/index.js +1 -0
- package/dist/MetalKit/functions.d.ts +11 -0
- package/dist/MetalKit/functions.js +20 -0
- package/dist/MetalKit/index.d.ts +1 -0
- package/dist/MetalKit/index.js +1 -0
- package/dist/MetalPerformanceShaders/functions.d.ts +7 -0
- package/dist/MetalPerformanceShaders/functions.js +14 -0
- package/dist/MetalPerformanceShaders/index.d.ts +1 -0
- package/dist/MetalPerformanceShaders/index.js +1 -0
- package/dist/NearbyInteraction/functions.d.ts +3 -0
- package/dist/NearbyInteraction/functions.js +5 -0
- package/dist/NearbyInteraction/index.d.ts +1 -0
- package/dist/NearbyInteraction/index.js +1 -0
- package/dist/ParavirtualizedGraphics/functions.d.ts +7 -0
- package/dist/ParavirtualizedGraphics/functions.js +14 -0
- package/dist/ParavirtualizedGraphics/index.d.ts +1 -0
- package/dist/ParavirtualizedGraphics/index.js +1 -0
- package/dist/QuartzCore/functions.d.ts +19 -0
- package/dist/QuartzCore/functions.js +50 -0
- package/dist/QuartzCore/index.d.ts +1 -0
- package/dist/QuartzCore/index.js +1 -0
- package/dist/SceneKit/functions.d.ts +17 -0
- package/dist/SceneKit/functions.js +38 -0
- package/dist/SceneKit/index.d.ts +1 -0
- package/dist/SceneKit/index.js +1 -0
- package/dist/SensorKit/functions.d.ts +4 -0
- package/dist/SensorKit/functions.js +14 -0
- package/dist/SensorKit/index.d.ts +1 -0
- package/dist/SensorKit/index.js +1 -0
- package/dist/ServiceManagement/functions.d.ts +7 -0
- package/dist/ServiceManagement/functions.js +20 -0
- package/dist/ServiceManagement/index.d.ts +1 -0
- package/dist/ServiceManagement/index.js +1 -0
- package/dist/StoreKit/functions.d.ts +1 -0
- package/dist/StoreKit/functions.js +5 -0
- package/dist/StoreKit/index.d.ts +1 -0
- package/dist/StoreKit/index.js +1 -0
- package/dist/VideoToolbox/functions.d.ts +81 -0
- package/dist/VideoToolbox/functions.js +236 -0
- package/dist/VideoToolbox/index.d.ts +1 -0
- package/dist/VideoToolbox/index.js +1 -0
- package/dist/Vision/functions.d.ts +16 -0
- package/dist/Vision/functions.js +38 -0
- package/dist/Vision/index.d.ts +1 -0
- package/dist/Vision/index.js +1 -0
- package/generator/ast-parser.ts +1368 -0
- package/generator/clang.ts +167 -0
- package/generator/custom.ts +936 -0
- package/generator/discover.ts +111 -0
- package/generator/emitter.ts +2026 -0
- package/generator/frameworks.ts +135 -0
- package/generator/index.ts +1334 -0
- package/generator/parse-worker.ts +263 -0
- package/generator/resolve-strings.ts +121 -0
- package/generator/struct-fields.ts +46 -0
- package/generator/templates/bind.ts +100 -0
- package/generator/templates/helpers.ts +70 -0
- package/generator/templates/nsdata.ts +97 -0
- package/generator/templates/osversion.ts +91 -0
- package/generator/type-mapper.ts +615 -0
- package/generator/worker-pool.ts +309 -0
- package/package.json +13 -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
|
+
});
|