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