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