expo-type-information 0.0.0 → 0.0.2

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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/bin/cli.js +2 -0
  4. package/build/cli.d.ts +1 -0
  5. package/build/cli.js +35 -0
  6. package/build/cli.js.map +1 -0
  7. package/build/commands/commandUtils.d.ts +46 -0
  8. package/build/commands/commandUtils.js +274 -0
  9. package/build/commands/commandUtils.js.map +1 -0
  10. package/build/commands/generateJSXIntrinsicsCommand.d.ts +2 -0
  11. package/build/commands/generateJSXIntrinsicsCommand.js +28 -0
  12. package/build/commands/generateJSXIntrinsicsCommand.js.map +1 -0
  13. package/build/commands/generateMocksForFileCommand.d.ts +2 -0
  14. package/build/commands/generateMocksForFileCommand.js +22 -0
  15. package/build/commands/generateMocksForFileCommand.js.map +1 -0
  16. package/build/commands/generateModuleTypesCommand.d.ts +2 -0
  17. package/build/commands/generateModuleTypesCommand.js +32 -0
  18. package/build/commands/generateModuleTypesCommand.js.map +1 -0
  19. package/build/commands/generateViewTypesCommand.d.ts +2 -0
  20. package/build/commands/generateViewTypesCommand.js +28 -0
  21. package/build/commands/generateViewTypesCommand.js.map +1 -0
  22. package/build/commands/inlineModulesInterfaceCommand.d.ts +2 -0
  23. package/build/commands/inlineModulesInterfaceCommand.js +129 -0
  24. package/build/commands/inlineModulesInterfaceCommand.js.map +1 -0
  25. package/build/commands/moduleInterfaceCommand.d.ts +2 -0
  26. package/build/commands/moduleInterfaceCommand.js +46 -0
  27. package/build/commands/moduleInterfaceCommand.js.map +1 -0
  28. package/build/commands/shortModuleInterfaceCommand.d.ts +2 -0
  29. package/build/commands/shortModuleInterfaceCommand.js +17 -0
  30. package/build/commands/shortModuleInterfaceCommand.js.map +1 -0
  31. package/build/commands/typeInformationCommand.d.ts +2 -0
  32. package/build/commands/typeInformationCommand.js +24 -0
  33. package/build/commands/typeInformationCommand.js.map +1 -0
  34. package/build/index.d.ts +3 -0
  35. package/build/index.js +28 -0
  36. package/build/index.js.map +1 -0
  37. package/build/mockgen.d.ts +4 -0
  38. package/build/mockgen.js +204 -0
  39. package/build/mockgen.js.map +1 -0
  40. package/build/swift/sourcekittenTypeInformation.d.ts +6 -0
  41. package/build/swift/sourcekittenTypeInformation.js +875 -0
  42. package/build/swift/sourcekittenTypeInformation.js.map +1 -0
  43. package/build/typeInformation.d.ts +209 -0
  44. package/build/typeInformation.js +157 -0
  45. package/build/typeInformation.js.map +1 -0
  46. package/build/types.d.ts +59 -0
  47. package/build/types.js +3 -0
  48. package/build/types.js.map +1 -0
  49. package/build/typescriptGeneration.d.ts +61 -0
  50. package/build/typescriptGeneration.js +696 -0
  51. package/build/typescriptGeneration.js.map +1 -0
  52. package/build/utils.d.ts +6 -0
  53. package/build/utils.js +44 -0
  54. package/build/utils.js.map +1 -0
  55. package/jest.config.js +6 -0
  56. package/package.json +46 -5
  57. package/src/cli.ts +38 -0
  58. package/src/commands/commandUtils.ts +352 -0
  59. package/src/commands/generateJSXIntrinsicsCommand.ts +38 -0
  60. package/src/commands/generateMocksForFileCommand.ts +30 -0
  61. package/src/commands/generateModuleTypesCommand.ts +39 -0
  62. package/src/commands/generateViewTypesCommand.ts +39 -0
  63. package/src/commands/inlineModulesInterfaceCommand.ts +175 -0
  64. package/src/commands/moduleInterfaceCommand.ts +56 -0
  65. package/src/commands/shortModuleInterfaceCommand.ts +26 -0
  66. package/src/commands/typeInformationCommand.ts +35 -0
  67. package/src/index.ts +9 -0
  68. package/src/mockgen.ts +338 -0
  69. package/src/swift/sourcekittenTypeInformation.ts +1173 -0
  70. package/src/typeInformation.ts +326 -0
  71. package/src/types.ts +68 -0
  72. package/src/typescriptGeneration.ts +1179 -0
  73. package/src/utils.ts +44 -0
  74. package/tests/TestModule.swift +175 -0
  75. package/tests/__snapshots__/typeInformation.test.ts.snap +1578 -0
  76. package/tests/typeInformation.test.ts +134 -0
  77. package/tsconfig.json +11 -0
@@ -0,0 +1,1173 @@
1
+ import { execSync, exec } from 'child_process';
2
+ import fs from 'fs';
3
+ import { promisify } from 'util';
4
+ import YAML from 'yaml';
5
+
6
+ import {
7
+ Argument,
8
+ BasicType,
9
+ ClassDeclaration,
10
+ ConstantDeclaration,
11
+ ConstructorDeclaration,
12
+ DefinitionOffset,
13
+ DictionaryType,
14
+ EnumType,
15
+ FileTypeInformation,
16
+ FunctionDeclaration,
17
+ IdentifierDefinition,
18
+ IdentifierKind,
19
+ ModuleClassDeclaration,
20
+ ParametrizedType,
21
+ PropDeclaration,
22
+ PropertyDeclaration,
23
+ RecordType,
24
+ SumType,
25
+ Type,
26
+ TypeIdentifier,
27
+ TypeIdentifierDefinitionMap,
28
+ TypeKind,
29
+ ViewDeclaration,
30
+ } from '../typeInformation';
31
+ import { Attribute, FileType, Structure } from '../types';
32
+ import { taskAll } from '../utils';
33
+
34
+ const execAsync = promisify(exec);
35
+
36
+ type SourcekittenClosure = {
37
+ parameters: { name: string; typename: string }[];
38
+ returnType: string | null;
39
+ };
40
+
41
+ const swiftDeclarationKind = {
42
+ enum: 'source.lang.swift.decl.enum',
43
+ struct: 'source.lang.swift.decl.struct',
44
+ class: 'source.lang.swift.decl.class',
45
+ varLocal: 'source.lang.swift.decl.var.local',
46
+ varInstance: 'source.lang.swift.decl.var.instance',
47
+ varParameter: 'source.lang.swift.decl.var.parameter',
48
+ closure: 'source.lang.swift.expr.closure',
49
+ enumcase: 'source.lang.swift.decl.enumcase',
50
+ };
51
+
52
+ function isSwiftDictionary(type: string): boolean {
53
+ return (
54
+ type.startsWith('[') &&
55
+ type.endsWith(']') &&
56
+ findRootColonInDictionary(type.substring(1, type.length - 1)) >= 0
57
+ );
58
+ }
59
+
60
+ function isSwiftArray(type: string) {
61
+ // This can also be an object, but we check that first, so if it's not an object and is wrapped with [] it's an array.
62
+ return type.startsWith('[') && type.endsWith(']');
63
+ }
64
+
65
+ function isSwiftOptional(type: string): boolean {
66
+ return type.endsWith('?');
67
+ }
68
+
69
+ function isParametrizedType(type: string): boolean {
70
+ return type.endsWith('>');
71
+ }
72
+
73
+ function isEitherTypeIdentifier(typeIdentifier: string): boolean {
74
+ return (
75
+ typeIdentifier === 'Either' ||
76
+ typeIdentifier === 'EitherOfThree' ||
77
+ typeIdentifier === 'EitherOfFour'
78
+ );
79
+ }
80
+
81
+ function isEnumStructure(structure: Structure): boolean {
82
+ return structure['key.kind'] === swiftDeclarationKind.enum;
83
+ }
84
+
85
+ function isRecordStructure(structure: Structure): boolean {
86
+ const isRecordOrClass =
87
+ structure['key.kind'] === swiftDeclarationKind.struct ||
88
+ structure['key.kind'] === swiftDeclarationKind.class;
89
+
90
+ const inheritsFromRecord =
91
+ structure['key.inheritedtypes']?.find((type) => {
92
+ return type['key.name'] === 'Record';
93
+ }) !== undefined;
94
+
95
+ return isRecordOrClass && inheritsFromRecord;
96
+ }
97
+
98
+ function isModuleStructure(structure: Structure): boolean {
99
+ return structure['key.typename'] === 'ModuleDefinition';
100
+ }
101
+
102
+ function unwrapSwiftArray(type: string): Type {
103
+ const innerType = type.substring(1, type.length - 1);
104
+ return mapSwiftTypeToTsType(innerType.trim());
105
+ }
106
+
107
+ function unwrapParametrizedType(type: string): ParametrizedType {
108
+ let openBracketCount = 0;
109
+ let start = 0;
110
+ const innerTypes: Type[] = [];
111
+ let name: string = '';
112
+ for (let i = 0; i < type.length; i += 1) {
113
+ if (type[i] === '<') {
114
+ openBracketCount += 1;
115
+ if (openBracketCount === 1) {
116
+ name = type.substring(0, i);
117
+ start = i + 1;
118
+ }
119
+ } else if (type[i] === '>') {
120
+ openBracketCount -= 1;
121
+ if (openBracketCount === 0) {
122
+ innerTypes.push(mapSwiftTypeToTsType(type.substring(start, i).trim()));
123
+ start = i + 1;
124
+ }
125
+ } else if (type[i] === ',' && openBracketCount === 1) {
126
+ innerTypes.push(mapSwiftTypeToTsType(type.substring(start, i).trim()));
127
+ start = i + 1;
128
+ }
129
+ }
130
+ return { name, types: innerTypes };
131
+ }
132
+
133
+ function unwrapSwiftDictionary(type: string) {
134
+ const innerType = type.substring(1, type.length - 1);
135
+ const colonPosition = findRootColonInDictionary(innerType);
136
+ return {
137
+ key: innerType.slice(0, colonPosition).trim(),
138
+ value: innerType.slice(colonPosition + 1).trim(),
139
+ };
140
+ }
141
+
142
+ /*
143
+ The Swift object type can have nested objects as the type of it's values (or maybe even keys).
144
+ [String: [String: Any]]
145
+
146
+ We can't use regex to find the root colon, so this is the safest way – by counting brackets.
147
+ */
148
+ function findRootColonInDictionary(type: string) {
149
+ let colonIndex = -1;
150
+ let openBracketsCount = 0;
151
+ for (let i = 0; i < type.length; i++) {
152
+ if (type[i] === '[') {
153
+ openBracketsCount++;
154
+ } else if (type[i] === ']') {
155
+ openBracketsCount--;
156
+ } else if (type[i] === ':' && openBracketsCount === 0) {
157
+ colonIndex = i;
158
+ break;
159
+ }
160
+ }
161
+ return colonIndex;
162
+ }
163
+
164
+ function mapSwiftTypeToTsType(type?: string): Type {
165
+ if (!type) {
166
+ return { kind: TypeKind.BASIC, type: BasicType.UNRESOLVED };
167
+ }
168
+
169
+ if (isSwiftOptional(type)) {
170
+ return { kind: TypeKind.OPTIONAL, type: mapSwiftTypeToTsType(type.slice(0, -1).trim()) };
171
+ }
172
+
173
+ if (isSwiftDictionary(type)) {
174
+ const { key, value } = unwrapSwiftDictionary(type);
175
+ const keyType = mapSwiftTypeToTsType(key);
176
+ const valueType = mapSwiftTypeToTsType(value);
177
+
178
+ return {
179
+ kind: TypeKind.DICTIONARY,
180
+ type: {
181
+ key: keyType,
182
+ value: valueType,
183
+ },
184
+ };
185
+ }
186
+
187
+ if (isSwiftArray(type)) {
188
+ return {
189
+ kind: TypeKind.ARRAY,
190
+ type: unwrapSwiftArray(type),
191
+ };
192
+ }
193
+
194
+ if (isParametrizedType(type)) {
195
+ const parametrizedType = unwrapParametrizedType(type);
196
+ if (isEitherTypeIdentifier(parametrizedType.name)) {
197
+ return {
198
+ kind: TypeKind.SUM,
199
+ type: parametrizedType as SumType,
200
+ };
201
+ }
202
+
203
+ return {
204
+ kind: TypeKind.PARAMETRIZED,
205
+ type: parametrizedType,
206
+ };
207
+ }
208
+
209
+ const returnType: Type = {
210
+ kind: TypeKind.BASIC,
211
+ type: BasicType.ANY,
212
+ };
213
+
214
+ switch (type) {
215
+ case 'unknown':
216
+ case 'Any':
217
+ returnType.type = BasicType.ANY;
218
+ break;
219
+ case 'String':
220
+ returnType.type = BasicType.STRING;
221
+ break;
222
+ case 'Bool':
223
+ returnType.type = BasicType.BOOLEAN;
224
+ break;
225
+ case 'Int':
226
+ case 'Float':
227
+ case 'Double':
228
+ returnType.type = BasicType.NUMBER;
229
+ break;
230
+ case 'Void':
231
+ returnType.type = BasicType.VOID;
232
+ break;
233
+ default:
234
+ returnType.kind = TypeKind.IDENTIFIER;
235
+ returnType.type = type;
236
+ }
237
+ return returnType;
238
+ }
239
+
240
+ function getStructureFromFile(file: FileType) {
241
+ const command = 'sourcekitten structure --file ' + file.path;
242
+
243
+ try {
244
+ const output = execSync(command, { maxBuffer: 10 * 1024 * 1024 });
245
+ return JSON.parse(output.toString());
246
+ } catch (error) {
247
+ console.error('An error occurred while executing the command:', error);
248
+ }
249
+ }
250
+
251
+ // Read string straight from file – needed since we can't get cursorinfo for modulename
252
+ function getIdentifierFromOffsetObject(offsetObject: Structure, file: FileType) {
253
+ const startIndex = offsetObject['key.offset'];
254
+ const endIndex = offsetObject['key.offset'] + offsetObject['key.length'];
255
+ return file.content.substring(startIndex, endIndex).replaceAll('"', '');
256
+ }
257
+
258
+ function hasSubstructure(structure: Structure) {
259
+ return structure?.['key.substructure'] && structure['key.substructure'].length > 0;
260
+ }
261
+
262
+ async function findReturnType(
263
+ structure: Structure,
264
+ file: FileType,
265
+ options: SwiftFileTypeInformationOptions
266
+ ): Promise<string | null> {
267
+ if (
268
+ structure['key.kind'] === swiftDeclarationKind.varLocal &&
269
+ structure['key.name'].startsWith('returnValueDeclaration_') &&
270
+ options.typeInference
271
+ ) {
272
+ // TODO(@HubertBer): this return type inference is really costly
273
+ return getTypeOfByteOffsetVariable(structure['key.nameoffset'], file);
274
+ }
275
+
276
+ if (hasSubstructure(structure)) {
277
+ for (const substructure of structure['key.substructure']) {
278
+ const returnType = findReturnType(substructure, file, options);
279
+ if (returnType) {
280
+ return returnType;
281
+ }
282
+ }
283
+ }
284
+ return null;
285
+ }
286
+
287
+ let cachedSDKPath: string | null = null;
288
+ function getSDKPath(): string | null {
289
+ if (cachedSDKPath) {
290
+ return cachedSDKPath;
291
+ }
292
+
293
+ cachedSDKPath = execSync('xcrun --sdk iphoneos --show-sdk-path')?.toString()?.trim();
294
+ if (!cachedSDKPath) {
295
+ console.error(`Couldn't find xcode sdk path!`);
296
+ return null;
297
+ }
298
+
299
+ return cachedSDKPath;
300
+ }
301
+
302
+ function getUnresolvedType(): Type {
303
+ return { kind: TypeKind.BASIC, type: BasicType.UNRESOLVED };
304
+ }
305
+
306
+ async function extractDeclarationType(
307
+ structure: Structure,
308
+ file: FileType,
309
+ options: SwiftFileTypeInformationOptions
310
+ ): Promise<Type> {
311
+ if (structure['key.typename']) {
312
+ return mapSwiftTypeToTsType(structure['key.typename'] as string);
313
+ }
314
+
315
+ // TODO(@HubertBer): this type inference is really costly
316
+ if (options.typeInference) {
317
+ const inferReturn = await getTypeOfByteOffsetVariable(structure['key.nameoffset'], file);
318
+ return inferReturn ? mapSwiftTypeToTsType(inferReturn) : getUnresolvedType();
319
+ }
320
+
321
+ return getUnresolvedType();
322
+ }
323
+
324
+ function constructSourcekiitenCursorInfoRequest({
325
+ filePath,
326
+ byteOffset,
327
+ sdkPath,
328
+ }: {
329
+ filePath: string;
330
+ byteOffset: number;
331
+ sdkPath: string;
332
+ }): string {
333
+ const request = {
334
+ 'key.request': 'source.request.cursorinfo',
335
+ 'key.sourcefile': filePath,
336
+ 'key.offset': byteOffset,
337
+ 'key.compilerargs': [filePath, '-target', 'arm64-apple-ios7', '-sdk', sdkPath],
338
+ };
339
+
340
+ const yamlRequest = YAML.stringify(request, {
341
+ defaultStringType: 'QUOTE_DOUBLE',
342
+ lineWidth: 0,
343
+ defaultKeyType: 'PLAIN',
344
+ })
345
+ .replace('"source.request.cursorinfo"', 'source.request.cursorinfo')
346
+ .replaceAll('"', '\\"');
347
+
348
+ return yamlRequest;
349
+ }
350
+
351
+ // Read type description with sourcekitten, works only for variables
352
+ // TODO(@HubertBer): This function is extremely slow and inefficient
353
+ // consider other options
354
+ async function getTypeOfByteOffsetVariable(
355
+ byteOffset: number,
356
+ file: FileType
357
+ ): Promise<string | null> {
358
+ const sdkPath = getSDKPath();
359
+ if (!sdkPath) {
360
+ return null;
361
+ }
362
+ const yamlRequest = constructSourcekiitenCursorInfoRequest({
363
+ filePath: file.path,
364
+ byteOffset,
365
+ sdkPath,
366
+ });
367
+ const command = 'sourcekitten request --yaml "' + yamlRequest + '"';
368
+ try {
369
+ const { stdout } = await execAsync(command);
370
+ const output = JSON.parse(stdout.toString());
371
+ const inferredType = output['key.typename'];
372
+ if (inferredType === '<<error type>>') {
373
+ return null;
374
+ }
375
+ return inferredType;
376
+ } catch (error) {
377
+ console.error('An error occurred while executing the command:', error);
378
+ }
379
+ return null;
380
+ }
381
+
382
+ function mapSourcekittenParameterToType(parameter: {
383
+ name: string | undefined;
384
+ typename: string;
385
+ }): Argument {
386
+ return {
387
+ name: parameter.name ?? undefined,
388
+ type: mapSwiftTypeToTsType(parameter.typename),
389
+ };
390
+ }
391
+
392
+ const parseModulePropertyStructure = parseModuleConstantStructure;
393
+
394
+ async function parseClosureTypes(
395
+ structure: Structure,
396
+ file: FileType,
397
+ options: SwiftFileTypeInformationOptions
398
+ ): Promise<SourcekittenClosure> {
399
+ const closure = structure['key.substructure']?.find(
400
+ (s) => s['key.kind'] === swiftDeclarationKind.closure
401
+ );
402
+ if (!closure) {
403
+ // Try finding the preprocessed return value, if not found we don't know the return type
404
+ const returnType = await findReturnType(structure, file, options);
405
+ return { parameters: [], returnType };
406
+ }
407
+
408
+ const parameters = closure['key.substructure']
409
+ ?.filter((s) => s['key.kind'] === swiftDeclarationKind.varParameter)
410
+ .map((p) => ({
411
+ name: p['key.name'] ?? undefined,
412
+ typename: p['key.typename'],
413
+ }));
414
+
415
+ const returnType = closure?.['key.typename'] ?? (await findReturnType(structure, file, options));
416
+ return { parameters, returnType };
417
+ }
418
+
419
+ async function parseModuleConstructorDeclaration(
420
+ substructure: Structure,
421
+ file: FileType,
422
+ options: SwiftFileTypeInformationOptions
423
+ ): Promise<ConstructorDeclaration> {
424
+ const definitionParams = substructure['key.substructure'];
425
+ let types = null;
426
+
427
+ // TODO(@HubertBer): rethink this maybe split based on what closure is expected
428
+ // Maybe this should be the last substructure
429
+ if (definitionParams[1] && hasSubstructure(definitionParams[1])) {
430
+ types = await parseClosureTypes(definitionParams[1], file, options);
431
+ } else if (definitionParams[0] && hasSubstructure(definitionParams[0])) {
432
+ types = await parseClosureTypes(definitionParams[0], file, options);
433
+ } else {
434
+ // TODO(@HubertBer): There sometimes might be another case which needs to be handled.
435
+ console.warn(`The type couldn't be resolved, this case is not yet implemented`);
436
+ // types = getTypeOfByteOffsetVariable(definitionParams[1]['key.offset'], file);
437
+ }
438
+
439
+ return {
440
+ arguments: types?.parameters.map(mapSourcekittenParameterToType) ?? [],
441
+ definitionOffset: substructure['key.offset'],
442
+ };
443
+ }
444
+
445
+ async function parseModuleConstantStructure(
446
+ substructure: Structure,
447
+ file: FileType,
448
+ options: SwiftFileTypeInformationOptions
449
+ ): Promise<ConstantDeclaration | null> {
450
+ const definitionParams = substructure['key.substructure'];
451
+ if (!definitionParams[0]) {
452
+ return null;
453
+ }
454
+
455
+ const name = getIdentifierFromOffsetObject(definitionParams[0], file);
456
+ let types = null;
457
+ if (definitionParams[1] && hasSubstructure(definitionParams[1])) {
458
+ types = await parseClosureTypes(definitionParams[1], file, options);
459
+ } else {
460
+ // TODO(@HubertBer): There sometimes might be another case which needs to be handled.
461
+ console.warn(`The type couldn't be resolved, this case is not yet implemented`);
462
+ // types = getTypeOfByteOffsetVariable(definitionParams[1]['key.offset'], file);
463
+ }
464
+
465
+ return {
466
+ name,
467
+ type: mapSwiftTypeToTsType(types?.returnType ?? undefined),
468
+ definitionOffset: substructure['key.offset'],
469
+ };
470
+ }
471
+
472
+ function getClosureBodyStructure(structure: Structure): Structure | null {
473
+ // Let's look at an example DSL class declaration
474
+ //
475
+ // Class(Blob.self) {
476
+ // Constructor { // ...
477
+ // // ...
478
+ //. }
479
+ // }
480
+ //
481
+ // The strucutre for a ClassDeclaration (from SourceKitten) looks like this:
482
+ // {
483
+ // "key.name": "Class",
484
+ // "key.substructure": [
485
+ // {
486
+ // "key.kind": "source.lang.swift.expr.argument", // 1st argument: `Blob.self`
487
+ // // ...
488
+ // },
489
+ // {
490
+ // "key.kind": "source.lang.swift.expr.argument", // 2nd argument: the closure
491
+ // "key.substructure": [
492
+ // {
493
+ // "key.kind": "source.lang.swift.expr.closure", // the closure
494
+ // "key.substructure": [
495
+ // {
496
+ // "key.kind": "source.lang.swift.stmt.brace", // the closure body
497
+ // "key.substructure": [
498
+ // {
499
+ // "key.kind": "source.lang.swift.expr.call", // DSL functions in the body
500
+ // "key.name": "Constructor",
501
+ // }, // ...
502
+ // ]
503
+ // }
504
+ // ]
505
+ // }
506
+ // ]
507
+ // }
508
+ // // ...
509
+ // }
510
+ //
511
+ // So to get to the closure body we need to take 1st argument, go in the closure definition and go in the closure body.
512
+ const classDeclarationClosureArgument = structure['key.substructure']?.[1];
513
+ const classDeclarationClosure = classDeclarationClosureArgument?.['key.substructure']?.[0];
514
+ const classDeclarationClosureBody = classDeclarationClosure?.['key.substructure']?.[0];
515
+ return classDeclarationClosureBody ?? null;
516
+ }
517
+
518
+ async function parseModuleClassStructure(
519
+ structure: Structure,
520
+ file: FileType,
521
+ options: SwiftFileTypeInformationOptions
522
+ ): Promise<ClassDeclaration> {
523
+ const nestedModuleSubstructure = getClosureBodyStructure(structure)?.['key.substructure'];
524
+ const nameSubstrucutre = structure['key.substructure']?.[0];
525
+ const name = nameSubstrucutre
526
+ ? getIdentifierFromOffsetObject(nameSubstrucutre, file).replace('.self', '')
527
+ : 'UnnamedClass';
528
+
529
+ if (!nestedModuleSubstructure) {
530
+ console.warn(name + " class is empty or couldn't parse its definition!");
531
+ return {
532
+ name,
533
+ constructor: null,
534
+ methods: [],
535
+ asyncMethods: [],
536
+ properties: [],
537
+ definitionOffset: structure['key.offset'],
538
+ };
539
+ }
540
+
541
+ // `parseModuleStructure` returns `ModuleClassDeclaration` with a found name or with the provided 'UNUSED_NAME', we don't need it here.
542
+ const classTypeInfo = await parseModuleStructure(
543
+ nestedModuleSubstructure,
544
+ file,
545
+ 'UNUSED_NAME',
546
+ structure['key.offset'],
547
+ options
548
+ );
549
+ return {
550
+ name,
551
+ methods: classTypeInfo.functions,
552
+ asyncMethods: classTypeInfo.asyncFunctions,
553
+ properties: classTypeInfo.properties,
554
+ constructor: classTypeInfo.constructor,
555
+ definitionOffset: structure['key.offset'],
556
+ };
557
+ }
558
+
559
+ async function parseModuleFunctionSubstructure(
560
+ substructure: Structure,
561
+ file: FileType,
562
+ options: SwiftFileTypeInformationOptions
563
+ ): Promise<FunctionDeclaration> {
564
+ const definitionParams = substructure['key.substructure'];
565
+ const nameSubstrucutre = definitionParams[0];
566
+ const name = nameSubstrucutre
567
+ ? getIdentifierFromOffsetObject(nameSubstrucutre, file)
568
+ : 'UnnamedFunction';
569
+ let types = null;
570
+ if (definitionParams[1] && hasSubstructure(definitionParams[1])) {
571
+ types = await parseClosureTypes(definitionParams[1], file, options);
572
+ } else {
573
+ // TODO(@HubertBer): There sometimes might be another case which needs to be handled.
574
+ console.warn(`The type couldn't be resolved, this case is not yet implemented`);
575
+ // types = getTypeOfByteOffsetVariable(definitionParams[1]['key.offset'], file);
576
+ }
577
+
578
+ return {
579
+ name,
580
+ returnType: mapSwiftTypeToTsType(types?.returnType ?? undefined), // any or void ? Probably any
581
+ parameters: [], // TODO(@HubertBer): Module function is not generic. I think so. Check it
582
+ arguments: types?.parameters?.map(mapSourcekittenParameterToType) ?? [],
583
+ definitionOffset: substructure['key.offset'],
584
+ };
585
+ }
586
+
587
+ async function parseModulePropDeclaration(
588
+ substructure: Structure,
589
+ file: FileType,
590
+ options: SwiftFileTypeInformationOptions
591
+ ): Promise<PropDeclaration> {
592
+ const definitionParams = substructure['key.substructure'];
593
+ const nameSubstrucutre = definitionParams[0];
594
+ const name = nameSubstrucutre
595
+ ? getIdentifierFromOffsetObject(nameSubstrucutre, file)
596
+ : 'UnkownProp';
597
+ let types = null;
598
+ if (definitionParams[1] && hasSubstructure(definitionParams[1])) {
599
+ types = await parseClosureTypes(definitionParams[1], file, options);
600
+ } else {
601
+ // TODO(@HubertBer): There sometimes might be another case which needs to be handled.
602
+ console.warn(`The type couldn't be resolved, this case is not yet implemented`);
603
+ // types = getTypeOfByteOffsetVariable(definitionParams[1]['key.offset'], file);
604
+ }
605
+
606
+ return {
607
+ name,
608
+ arguments: types?.parameters?.map(mapSourcekittenParameterToType) ?? [],
609
+ definitionOffset: substructure['key.offset'],
610
+ };
611
+ }
612
+
613
+ async function parseModuleViewDeclaration(
614
+ substructure: Structure,
615
+ file: FileType,
616
+ options: SwiftFileTypeInformationOptions
617
+ ): Promise<ViewDeclaration | null> {
618
+ // The View arguments is a.self for some class a we want.
619
+ const suffixLength = 5;
620
+ const nameSubstrucutre = substructure['key.substructure']?.[0];
621
+ if (!nameSubstrucutre) {
622
+ return null;
623
+ }
624
+
625
+ const name = getIdentifierFromOffsetObject(nameSubstrucutre, file).slice(0, -suffixLength);
626
+ const viewStructure = getClosureBodyStructure(substructure);
627
+ const viewSubstructure = viewStructure?.['key.substructure'];
628
+ if (!viewSubstructure) {
629
+ return null;
630
+ }
631
+
632
+ return await parseModuleStructure(
633
+ viewSubstructure,
634
+ file,
635
+ name,
636
+ viewStructure['key.offset'],
637
+ options
638
+ );
639
+ }
640
+
641
+ function parseModuleEventDeclaration(structure: Structure, file: FileType, events: string[]) {
642
+ structure['key.substructure'].forEach((substructure) =>
643
+ events.push(getIdentifierFromOffsetObject(substructure, file))
644
+ );
645
+ }
646
+
647
+ function hasFieldAttribute(attributes: Attribute[] | null, file: FileType): boolean {
648
+ if (!attributes) {
649
+ return false;
650
+ }
651
+
652
+ return attributes.some((attribute) => {
653
+ const startIndex = attribute['key.offset'];
654
+ const length = attribute['key.length'];
655
+ return (
656
+ length === '@Field'.length &&
657
+ file.content.substring(startIndex, startIndex + length) === '@Field'
658
+ );
659
+ });
660
+ }
661
+
662
+ async function parseRecordStructure(
663
+ recordStructure: Structure,
664
+ usedTypeIdentifiers: Set<string>,
665
+ inferredTypeParametersCount: Map<string, number>,
666
+ file: FileType,
667
+ options: SwiftFileTypeInformationOptions
668
+ ): Promise<RecordType> {
669
+ const recordSubstrucutres = recordStructure['key.substructure'].filter(
670
+ (substructure) =>
671
+ substructure['key.kind'] === swiftDeclarationKind.varInstance &&
672
+ hasFieldAttribute(substructure['key.attributes'], file)
673
+ );
674
+
675
+ const fields = await taskAll(recordSubstrucutres, async (substructure) => {
676
+ const type = await extractDeclarationType(substructure, file, options);
677
+ return { type, name: substructure['key.name'] };
678
+ });
679
+
680
+ fields.forEach(({ type }) => {
681
+ collectTypeIdentifiers(type, usedTypeIdentifiers, inferredTypeParametersCount);
682
+ });
683
+
684
+ return {
685
+ name: recordStructure['key.name'],
686
+ fields,
687
+ };
688
+ }
689
+
690
+ function parseEnumStructure(enumStructure: Structure): EnumType {
691
+ const enumcases: string[] = enumStructure['key.substructure']
692
+ .filter((sub) => sub['key.kind'] === swiftDeclarationKind.enumcase)
693
+ .flatMap((sub) => sub['key.substructure'])
694
+ .map((sub) => sub['key.name'].split('(', 1)[0])
695
+ .filter((enumcase) => enumcase !== undefined);
696
+
697
+ return {
698
+ name: enumStructure['key.name'],
699
+ cases: enumcases,
700
+ };
701
+ }
702
+
703
+ function sortModuleClassDeclaration(moduleClassDeclaration: ModuleClassDeclaration) {
704
+ const cmp = (obj0: DefinitionOffset, obj1: DefinitionOffset): number =>
705
+ obj0.definitionOffset - obj1.definitionOffset;
706
+
707
+ moduleClassDeclaration.asyncFunctions.sort(cmp);
708
+ moduleClassDeclaration.classes.sort(cmp);
709
+ moduleClassDeclaration.constants.sort(cmp);
710
+ moduleClassDeclaration.events.sort();
711
+ moduleClassDeclaration.functions.sort(cmp);
712
+ moduleClassDeclaration.properties.sort(cmp);
713
+ moduleClassDeclaration.props.sort(cmp);
714
+ moduleClassDeclaration.views.sort(cmp);
715
+ }
716
+
717
+ function parsePropertyString(
718
+ property: string,
719
+ definitionOffset: number
720
+ ): PropertyDeclaration | null {
721
+ const propertyRegex = /Property\(\.\s*"([^"]*)"\s*\)/;
722
+ const matches = property.match(propertyRegex);
723
+ const propertyName = matches?.[1];
724
+ if (!matches || !propertyName) {
725
+ return null;
726
+ }
727
+
728
+ return {
729
+ name: propertyName,
730
+ type: {
731
+ kind: TypeKind.BASIC,
732
+ type: BasicType.UNRESOLVED,
733
+ },
734
+ definitionOffset,
735
+ };
736
+ }
737
+
738
+ async function parseModuleStructure(
739
+ moduleStructure: Structure[],
740
+ file: FileType,
741
+ name: string,
742
+ definitionOffset: number,
743
+ options: SwiftFileTypeInformationOptions
744
+ ): Promise<ModuleClassDeclaration> {
745
+ const moduleClassDeclaration: ModuleClassDeclaration = {
746
+ name,
747
+ constants: [],
748
+ constructor: null,
749
+ functions: [],
750
+ asyncFunctions: [],
751
+ classes: [],
752
+ properties: [],
753
+ props: [],
754
+ views: [],
755
+ events: [],
756
+ definitionOffset,
757
+ };
758
+
759
+ await taskAll(moduleStructure, async (structure) => {
760
+ // TODO(@HubertBer): Some special cases when the sourcekitten parses the structure differently, for now only Property as it is common
761
+ if (structure['key.name'].startsWith('Property(')) {
762
+ const propertyDeclaration = parsePropertyString(
763
+ structure['key.name'],
764
+ structure['key.nameoffset']
765
+ );
766
+ if (propertyDeclaration) {
767
+ moduleClassDeclaration.properties.push(propertyDeclaration);
768
+ }
769
+ return;
770
+ }
771
+
772
+ switch (structure['key.name']) {
773
+ case 'Name': {
774
+ const nameSubstrucutre = structure['key.substructure']?.[0];
775
+ if (nameSubstrucutre) {
776
+ moduleClassDeclaration.name = getIdentifierFromOffsetObject(nameSubstrucutre, file);
777
+ }
778
+ break;
779
+ }
780
+ case 'Function': {
781
+ moduleClassDeclaration.functions.push(
782
+ await parseModuleFunctionSubstructure(structure, file, options)
783
+ );
784
+ break;
785
+ }
786
+ case 'Constant': {
787
+ const constantDeclaration = await parseModuleConstantStructure(structure, file, options);
788
+ if (constantDeclaration) {
789
+ moduleClassDeclaration.constants.push(constantDeclaration);
790
+ }
791
+ break;
792
+ }
793
+ case 'Class':
794
+ moduleClassDeclaration.classes.push(
795
+ await parseModuleClassStructure(structure, file, options)
796
+ );
797
+ break;
798
+ case 'Property': {
799
+ const propertyDeclaration = await parseModulePropertyStructure(structure, file, options);
800
+ if (propertyDeclaration) {
801
+ moduleClassDeclaration.properties.push(propertyDeclaration);
802
+ }
803
+ break;
804
+ }
805
+ case 'AsyncFunction':
806
+ moduleClassDeclaration.asyncFunctions.push(
807
+ await parseModuleFunctionSubstructure(structure, file, options)
808
+ );
809
+ break;
810
+ case 'Constructor':
811
+ moduleClassDeclaration.constructor = await parseModuleConstructorDeclaration(
812
+ structure,
813
+ file,
814
+ options
815
+ );
816
+ break;
817
+ case 'Prop':
818
+ moduleClassDeclaration.props.push(
819
+ await parseModulePropDeclaration(structure, file, options)
820
+ );
821
+ break;
822
+ case 'View': {
823
+ const viewDeclaration = await parseModuleViewDeclaration(structure, file, options);
824
+ if (viewDeclaration) {
825
+ moduleClassDeclaration.views.push(viewDeclaration);
826
+ }
827
+ break;
828
+ }
829
+ case 'Events':
830
+ parseModuleEventDeclaration(structure, file, moduleClassDeclaration.events);
831
+ break;
832
+ default:
833
+ console.warn(`Module substructure not supported. ${structure['key.name']}`);
834
+ }
835
+ });
836
+
837
+ // As we parse the module structure concurrently the order of for example functions is nondeterministic.
838
+ // We want to make it deterministic -- better for testing and usage.
839
+ //
840
+ // To make it deterministic a `definitionOffset` was added to each declaration.
841
+ // We sort declaration by this `definitionOffset` which additionally preserves the in file ordering.
842
+ //
843
+ // This may not be as useful if we get to merging type informations from multiple files as the `definitionOffset` will not be comparable.
844
+ sortModuleClassDeclaration(moduleClassDeclaration);
845
+ return moduleClassDeclaration;
846
+ }
847
+
848
+ function parseStructure(
849
+ structure: Structure,
850
+ name: string,
851
+ modulesStructures: { structure: Structure; name: string }[],
852
+ recordsStructures: Structure[],
853
+ enumsStructures: Structure[]
854
+ ) {
855
+ // TODO(@HubertBer): Find out why sometimes the structure is undefined (for example when parsing expo-audio)
856
+ if (!structure || !structure['key.substructure']) {
857
+ return;
858
+ }
859
+ const substructure = structure['key.substructure'];
860
+
861
+ if (isModuleStructure(structure)) {
862
+ modulesStructures.push({ structure, name });
863
+ } else if (isRecordStructure(structure)) {
864
+ recordsStructures.push(structure);
865
+ } else if (isEnumStructure(structure)) {
866
+ enumsStructures.push(structure);
867
+ } else if (Array.isArray(substructure) && substructure.length > 0) {
868
+ for (const substructure of structure['key.substructure']) {
869
+ parseStructure(
870
+ substructure,
871
+ structure['key.name'] ?? name,
872
+ modulesStructures,
873
+ recordsStructures,
874
+ enumsStructures
875
+ );
876
+ }
877
+ }
878
+ }
879
+
880
+ function getTypeIdentifierDefinitionMap(
881
+ fileTypeInformation: FileTypeInformation
882
+ ): Map<string, IdentifierDefinition> {
883
+ const typeIdentifierDefinitionMap = new Map<
884
+ string,
885
+ { kind: IdentifierKind; definition: string | RecordType | EnumType | ClassDeclaration }
886
+ >([]);
887
+
888
+ fileTypeInformation.records.forEach((r) =>
889
+ typeIdentifierDefinitionMap.set(r.name, { kind: IdentifierKind.RECORD, definition: r })
890
+ );
891
+ fileTypeInformation.enums.forEach((e) =>
892
+ typeIdentifierDefinitionMap.set(e.name, { kind: IdentifierKind.ENUM, definition: e })
893
+ );
894
+
895
+ return typeIdentifierDefinitionMap;
896
+ }
897
+
898
+ function collectTypeIdentifiers(
899
+ type: Type,
900
+ typeIdentiers: Set<string>,
901
+ inferredTypeParametersCount: Map<string, number>
902
+ ) {
903
+ switch (type.kind) {
904
+ case TypeKind.ARRAY:
905
+ case TypeKind.OPTIONAL:
906
+ collectTypeIdentifiers(type.type as Type, typeIdentiers, inferredTypeParametersCount);
907
+ break;
908
+ case TypeKind.DICTIONARY:
909
+ collectTypeIdentifiers(
910
+ (type.type as DictionaryType).key,
911
+ typeIdentiers,
912
+ inferredTypeParametersCount
913
+ );
914
+ collectTypeIdentifiers(
915
+ (type.type as DictionaryType).value,
916
+ typeIdentiers,
917
+ inferredTypeParametersCount
918
+ );
919
+ break;
920
+ case TypeKind.SUM:
921
+ for (const t of (type.type as SumType).types) {
922
+ collectTypeIdentifiers(t, typeIdentiers, inferredTypeParametersCount);
923
+ }
924
+ break;
925
+ case TypeKind.BASIC:
926
+ break;
927
+ case TypeKind.IDENTIFIER:
928
+ typeIdentiers.add(type.type as TypeIdentifier);
929
+ break;
930
+ case TypeKind.PARAMETRIZED: {
931
+ const parametrizedType: ParametrizedType = type.type as ParametrizedType;
932
+ const typename = parametrizedType.name;
933
+ typeIdentiers.add(typename);
934
+ inferredTypeParametersCount.set(
935
+ typename,
936
+ Math.max(inferredTypeParametersCount.get(typename) ?? 0, parametrizedType.types.length)
937
+ );
938
+ for (const t of (type.type as ParametrizedType).types) {
939
+ collectTypeIdentifiers(t, typeIdentiers, inferredTypeParametersCount);
940
+ }
941
+ break;
942
+ }
943
+ }
944
+ }
945
+
946
+ function collectModuleTypeIdentifiers(
947
+ moduleClassDeclaration: ModuleClassDeclaration,
948
+ fileTypeInformation: FileTypeInformation
949
+ ) {
950
+ const collect = (type: Type) => {
951
+ collectTypeIdentifiers(
952
+ type,
953
+ fileTypeInformation.usedTypeIdentifiers,
954
+ fileTypeInformation.inferredTypeParametersCount
955
+ );
956
+ };
957
+ const collectArg = (arg: Argument) => {
958
+ collect(arg.type);
959
+ };
960
+ const collectFunction = (functionDeclaration: FunctionDeclaration) => {
961
+ collect(functionDeclaration.returnType);
962
+ functionDeclaration.arguments.forEach(collectArg);
963
+ functionDeclaration.parameters.forEach(collect);
964
+ };
965
+ moduleClassDeclaration.asyncFunctions.forEach(collectFunction);
966
+ moduleClassDeclaration.functions.forEach(collectFunction);
967
+ moduleClassDeclaration.constants.forEach(collectArg);
968
+ moduleClassDeclaration.properties.forEach(collectArg);
969
+ moduleClassDeclaration.constructor?.arguments.forEach(collectArg);
970
+ moduleClassDeclaration.views.forEach((v) => collectModuleTypeIdentifiers(v, fileTypeInformation));
971
+ moduleClassDeclaration.props.forEach((p) => p.arguments.forEach(collectArg));
972
+ moduleClassDeclaration.classes.forEach((c) => {
973
+ fileTypeInformation.declaredTypeIdentifiers.add(c.name);
974
+ c.asyncMethods.forEach(collectFunction);
975
+ c.methods.forEach(collectFunction);
976
+ c.constructor?.arguments.forEach(collectArg);
977
+ c.properties.forEach(collectArg);
978
+ });
979
+ }
980
+
981
+ export type SwiftFileTypeInformationOptions = {
982
+ typeInference: boolean;
983
+ };
984
+
985
+ export async function getSwiftFileTypeInformation(
986
+ filePath: string,
987
+ options: SwiftFileTypeInformationOptions
988
+ ): Promise<FileTypeInformation | null> {
989
+ const file = { path: filePath, content: fs.readFileSync(filePath, 'utf8') };
990
+
991
+ const modulesStructures: { name: string; structure: Structure }[] = [];
992
+ const recordsStructures: Structure[] = [];
993
+ const enumsStructures: Structure[] = [];
994
+ parseStructure(
995
+ getStructureFromFile(file),
996
+ '',
997
+ modulesStructures,
998
+ recordsStructures,
999
+ enumsStructures
1000
+ );
1001
+
1002
+ const inferredTypeParametersCount = new Map<string, number>();
1003
+ const moduleClasses: ModuleClassDeclaration[] = [];
1004
+ const moduleTypeIdentifiers = new Set<string>();
1005
+ const declaredTypeIdentifiers = new Set<string>();
1006
+ const recordTypeIdentifiers = new Set<string>();
1007
+ const typeIdentifierDefinitionMap: TypeIdentifierDefinitionMap = new Map();
1008
+ const enums: EnumType[] = enumsStructures.map(parseEnumStructure);
1009
+ const recordMap = (rd: Structure) => {
1010
+ return parseRecordStructure(
1011
+ rd,
1012
+ recordTypeIdentifiers,
1013
+ inferredTypeParametersCount,
1014
+ file,
1015
+ options
1016
+ );
1017
+ };
1018
+
1019
+ const recordsPromise = taskAll(recordsStructures, recordMap);
1020
+ const moduleClassDeclarationsPromise = taskAll(
1021
+ modulesStructures.filter(({ structure }) => hasSubstructure(structure)),
1022
+ ({ structure, name }) =>
1023
+ parseModuleStructure(
1024
+ structure['key.substructure'],
1025
+ file,
1026
+ name,
1027
+ structure['key.offset'],
1028
+ options
1029
+ )
1030
+ );
1031
+
1032
+ const [records, moduleClassDeclarations] = await Promise.all([
1033
+ recordsPromise,
1034
+ moduleClassDeclarationsPromise,
1035
+ ]);
1036
+
1037
+ enums.forEach(({ name }) => {
1038
+ declaredTypeIdentifiers.add(name);
1039
+ });
1040
+ records.forEach(({ name }) => {
1041
+ declaredTypeIdentifiers.add(name);
1042
+ });
1043
+
1044
+ const fileTypeInformation = {
1045
+ moduleClasses,
1046
+ records,
1047
+ enums,
1048
+ functions: [],
1049
+ usedTypeIdentifiers: moduleTypeIdentifiers.union(recordTypeIdentifiers),
1050
+ declaredTypeIdentifiers,
1051
+ inferredTypeParametersCount,
1052
+ typeIdentifierDefinitionMap,
1053
+ };
1054
+
1055
+ for (const moduleClassDeclaration of moduleClassDeclarations) {
1056
+ moduleClasses.push(moduleClassDeclaration);
1057
+ collectModuleTypeIdentifiers(moduleClassDeclaration, fileTypeInformation);
1058
+ }
1059
+
1060
+ fileTypeInformation.typeIdentifierDefinitionMap =
1061
+ getTypeIdentifierDefinitionMap(fileTypeInformation);
1062
+
1063
+ return fileTypeInformation;
1064
+ }
1065
+
1066
+ function removeComments(fileContent: string): string {
1067
+ // This regex matches doubly quoted strings ("string"), and comments (`// comment` and `/* comment */`).
1068
+ //
1069
+ // It is in a form A|B where:
1070
+ // A = ("(?:[^"\\]|\\.)*")
1071
+ // Matches and captures doubly quoted strings ("string")
1072
+ //
1073
+ // B = (\/\/.*|\/\*[\s\S]*?\*\/)
1074
+ // Matches and captures comments (`// comment` and `/* comment */`)
1075
+
1076
+ // By first matching strings we ensure that we don't match comments which happen to be inside a string literal.
1077
+ // This regex doesn't handle:
1078
+ // - multline strings literals """ multiline """
1079
+ // - nested comments /* comment /* nested comment */ */
1080
+ const commentRegex = /("(?:[^"\\]|\\.)*")|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
1081
+ return fileContent.replace(commentRegex, (match, doubleQuoted) => {
1082
+ if (doubleQuoted) {
1083
+ return match;
1084
+ }
1085
+ return '';
1086
+ });
1087
+ }
1088
+
1089
+ function returnExpressionEnd(fileContent: string, returnIndex: number): number {
1090
+ let inString = false;
1091
+ let escaped = false;
1092
+ let parenCount = 0;
1093
+ let braceCount = 0;
1094
+ // TODO(@HubertBer): figure out what also changes the typical end of expression
1095
+
1096
+ let i = returnIndex;
1097
+ while (i < fileContent.length) {
1098
+ const char = fileContent[i];
1099
+ let escapedNow = false;
1100
+ switch (char) {
1101
+ case '(':
1102
+ parenCount += 1;
1103
+ break;
1104
+ case ')':
1105
+ parenCount -= 1;
1106
+ break;
1107
+ case '{':
1108
+ braceCount += 1;
1109
+ break;
1110
+ case '}':
1111
+ if (braceCount === 0) {
1112
+ return i;
1113
+ }
1114
+ braceCount -= 1;
1115
+ break;
1116
+ case '"':
1117
+ if (!escaped) {
1118
+ inString = !inString;
1119
+ }
1120
+ break;
1121
+ case ';':
1122
+ return i;
1123
+ case '\n':
1124
+ case '\r':
1125
+ if (!inString && parenCount === 0 && braceCount === 0) {
1126
+ return i;
1127
+ }
1128
+ break;
1129
+ case '\\':
1130
+ escapedNow = true;
1131
+ }
1132
+ escaped = escapedNow;
1133
+ i += 1;
1134
+ }
1135
+ return i;
1136
+ }
1137
+
1138
+ // Preprocessing to help sourcekitten functions
1139
+ // For now we create a new variable for each return statement,
1140
+ // we can find it's type easily with sourcekitten
1141
+ // TODO(@HubertBer): This has many problems which need fixing:
1142
+ // - return can be inside a string
1143
+ // - return Expression end parses incorrectly in case of some strings (check how it parses expo-video)
1144
+ export function preprocessSwiftFile(originalFileContent: string): string {
1145
+ const newFileContent: string[] = [];
1146
+ const fileContent = removeComments(originalFileContent);
1147
+ const returnPositions: { start: number; end: number }[] = [];
1148
+ let startPos = 0;
1149
+ while (startPos < fileContent.length) {
1150
+ const returnIndex = fileContent.indexOf('return ', startPos);
1151
+ if (returnIndex < 0 || returnIndex >= fileContent.length) {
1152
+ break;
1153
+ }
1154
+ returnPositions.push({
1155
+ start: returnIndex,
1156
+ end: returnExpressionEnd(fileContent, returnIndex),
1157
+ });
1158
+ startPos = returnIndex + 1;
1159
+ }
1160
+
1161
+ let prevEnd = 0;
1162
+
1163
+ for (const { start, end } of returnPositions) {
1164
+ newFileContent.push(fileContent.substring(prevEnd, start));
1165
+ newFileContent.push(
1166
+ `\nlet returnValueDeclaration_${start}_${end} = ${fileContent.substring(start + 6, end)}\n`
1167
+ );
1168
+ newFileContent.push(`return returnValueDeclaration_${start}_${end}\n`);
1169
+ prevEnd = end;
1170
+ }
1171
+ newFileContent.push(fileContent.substring(prevEnd, fileContent.length));
1172
+ return newFileContent.join('');
1173
+ }