1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3    return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.getAllExpoModulesInWorkingDirectory = void 0;
7// convert requires above to imports
8const child_process_1 = require("child_process");
9const fs_1 = __importDefault(require("fs"));
10const glob_1 = require("glob");
11const xml_js_1 = __importDefault(require("xml-js"));
12const yaml_1 = __importDefault(require("yaml"));
13const rootDir = process.cwd();
14const pattern = `${rootDir}/**/*.swift`;
15function getStructureFromFile(file) {
16    const command = 'sourcekitten structure --file ' + file.path;
17    try {
18        const output = (0, child_process_1.execSync)(command);
19        return JSON.parse(output.toString());
20    }
21    catch (error) {
22        console.error('An error occurred while executing the command:', error);
23    }
24}
25// find an object with "key.typename" : "ModuleDefinition" somewhere in the structure and return it
26function findModuleDefinitionInStructure(structure) {
27    if (!structure) {
28        return null;
29    }
30    if (structure?.['key.typename'] === 'ModuleDefinition') {
31        const root = structure?.['key.substructure'];
32        if (!root) {
33            console.warn('Found ModuleDefinition but it is malformed');
34        }
35        return root;
36    }
37    const substructure = structure['key.substructure'];
38    if (Array.isArray(substructure) && substructure.length > 0) {
39        for (const child of substructure) {
40            let result = null;
41            result = findModuleDefinitionInStructure(child);
42            if (result) {
43                return result;
44            }
45        }
46    }
47    return null;
48}
49// Read string straight from file – needed since we can't get cursorinfo for modulename
50function getIdentifierFromOffsetObject(offsetObject, file) {
51    // adding 1 and removing 1 to get rid of quotes
52    return file.content
53        .substring(offsetObject['key.offset'], offsetObject['key.offset'] + offsetObject['key.length'])
54        .replaceAll('"', '');
55}
56function maybeUnwrapXMLStructs(type) {
57    if (!type) {
58        return type;
59    }
60    if (typeof type === 'string') {
61        return type;
62    }
63    if (type['_text']) {
64        return type['_text'];
65    }
66    if (type['ref.struct']) {
67        return maybeUnwrapXMLStructs(type['ref.struct']);
68    }
69    return type;
70}
71function maybeWrapArray(itemOrItems) {
72    if (!itemOrItems) {
73        return null;
74    }
75    if (Array.isArray(itemOrItems)) {
76        return itemOrItems;
77    }
78    else {
79        return [itemOrItems];
80    }
81}
82function parseXMLAnnotatedDeclarations(cursorInfoOutput) {
83    const xml = cursorInfoOutput['key.fully_annotated_decl'];
84    if (!xml) {
85        return null;
86    }
87    const parsed = xml_js_1.default.xml2js(xml, { compact: true });
88    const parameters = maybeWrapArray(parsed?.['decl.function.free']?.['decl.var.parameter'])?.map((p) => ({
89        name: maybeUnwrapXMLStructs(p['decl.var.parameter.argument_label']),
90        typename: maybeUnwrapXMLStructs(p['decl.var.parameter.type']),
91    })) ?? [];
92    const returnType = maybeUnwrapXMLStructs(parsed?.['decl.function.free']?.['decl.function.returntype']);
93    return { parameters, returnType };
94}
95let cachedSDKPath = null;
96function getSDKPath() {
97    if (cachedSDKPath) {
98        return cachedSDKPath;
99    }
100    const sdkPath = (0, child_process_1.execSync)('xcrun --sdk iphoneos --show-sdk-path').toString().trim();
101    cachedSDKPath = sdkPath;
102    return cachedSDKPath;
103}
104// Read type description with sourcekitten, works only for variables
105function getTypeFromOffsetObject(offsetObject, file) {
106    if (!offsetObject) {
107        return null;
108    }
109    const request = {
110        'key.request': 'source.request.cursorinfo',
111        'key.sourcefile': file.path,
112        'key.offset': offsetObject['key.offset'],
113        'key.compilerargs': [file.path, '-target', 'arm64-apple-ios', '-sdk', getSDKPath()],
114    };
115    const yamlRequest = yaml_1.default.stringify(request, {
116        defaultStringType: 'QUOTE_DOUBLE',
117        lineWidth: 0,
118        defaultKeyType: 'PLAIN',
119        // needed since behaviour of sourcekitten is not consistent
120    }).replace('"source.request.cursorinfo"', 'source.request.cursorinfo');
121    const command = 'sourcekitten request --yaml "' + yamlRequest.replaceAll('"', '\\"') + '"';
122    try {
123        const output = (0, child_process_1.execSync)(command, { stdio: 'pipe' });
124        return parseXMLAnnotatedDeclarations(JSON.parse(output.toString()));
125    }
126    catch (error) {
127        console.error('An error occurred while executing the command:', error);
128    }
129    return null;
130}
131function hasSubstructure(structureObject) {
132    return structureObject?.['key.substructure'] && structureObject['key.substructure'].length > 0;
133}
134function parseClosureTypes(structureObject) {
135    const closure = structureObject['key.substructure']?.find((s) => s['key.kind'] === 'source.lang.swift.expr.closure');
136    if (!closure) {
137        return null;
138    }
139    const parameters = closure['key.substructure']
140        ?.filter((s) => s['key.kind'] === 'source.lang.swift.decl.var.parameter')
141        .map((p) => ({ name: p['key.name'], typename: p['key.typename'] }));
142    // TODO: Figure out if possible
143    const returnType = 'unknown';
144    return { parameters, returnType };
145}
146// Used for functions,async functions, all of shape Identifier(name, closure or function)
147function findNamedDefinitionsOfType(type, moduleDefinition, file) {
148    const definitionsOfType = moduleDefinition.filter((md) => md['key.name'] === type);
149    return definitionsOfType.map((d) => {
150        const definitionParams = d['key.substructure'];
151        const name = getIdentifierFromOffsetObject(definitionParams[0], file);
152        let types = null;
153        if (hasSubstructure(definitionParams[1])) {
154            types = parseClosureTypes(definitionParams[1]);
155        }
156        else {
157            types = getTypeFromOffsetObject(definitionParams[1], file);
158        }
159        return { name, types };
160    });
161}
162// Used for events
163function findGroupedDefinitionsOfType(type, moduleDefinition, file) {
164    const definitionsOfType = moduleDefinition.filter((md) => md['key.name'] === type);
165    return definitionsOfType.flatMap((d) => {
166        const definitionParams = d['key.substructure'];
167        return definitionParams.map((d) => ({ name: getIdentifierFromOffsetObject(d, file) }));
168    });
169}
170function findAndParseView(moduleDefinition, file) {
171    const viewDefinition = moduleDefinition.find((md) => md['key.name'] === 'View');
172    if (!viewDefinition) {
173        return null;
174    }
175    // we support reading view definitions from closure only
176    const viewModuleDefinition = viewDefinition['key.substructure']?.[1]?.['key.substructure']?.[0]?.['key.substructure']?.[0]?.['key.substructure'];
177    if (!viewModuleDefinition) {
178        console.warn('Could not parse view definition');
179        return null;
180    }
181    // let's drop nested view field (is null anyways)
182    const { view: _, ...definition } = parseModuleDefinition(viewModuleDefinition, file);
183    return definition;
184}
185function omitViewFromClosureArguments(definitions) {
186    return definitions.map((d) => ({
187        ...d,
188        types: {
189            ...d.types,
190            parameters: d.types?.parameters?.filter((t, idx) => idx !== 0 && t.name !== 'view'),
191        },
192    }));
193}
194function parseModuleDefinition(moduleDefinition, file) {
195    const parsedDefinition = {
196        name: findNamedDefinitionsOfType('Name', moduleDefinition, file)?.[0]?.name,
197        functions: findNamedDefinitionsOfType('Function', moduleDefinition, file),
198        asyncFunctions: findNamedDefinitionsOfType('AsyncFunction', moduleDefinition, file),
199        events: findGroupedDefinitionsOfType('Events', moduleDefinition, file),
200        properties: findNamedDefinitionsOfType('Property', moduleDefinition, file),
201        props: omitViewFromClosureArguments(findNamedDefinitionsOfType('Prop', moduleDefinition, file)),
202        view: findAndParseView(moduleDefinition, file),
203    };
204    return parsedDefinition;
205}
206function findModuleDefinitionsInFiles(files) {
207    const modules = [];
208    for (const path of files) {
209        const file = { path, content: fs_1.default.readFileSync(path, 'utf8') };
210        const definition = findModuleDefinitionInStructure(getStructureFromFile(file));
211        if (definition) {
212            modules.push(parseModuleDefinition(definition, file));
213        }
214    }
215    return modules;
216}
217function getAllExpoModulesInWorkingDirectory() {
218    const files = (0, glob_1.globSync)(pattern);
219    return findModuleDefinitionsInFiles(files);
220}
221exports.getAllExpoModulesInWorkingDirectory = getAllExpoModulesInWorkingDirectory;
222//# sourceMappingURL=getStructure.js.map