UNPKG

swagger-autogen-validator

Version:

This module performs the automatic construction of the Swagger documentation. The module can identify the endpoints and automatically capture methods such as to get, post, put, and so on. The module can also identify the paths, routes, middlewares, respon

880 lines (774 loc) 138 kB
const fs = require('fs'); const JSON5 = require('json5'); const merge = require('deepmerge'); const swaggerTags = require('./swagger-tags'); const handleData = require('./handle-data'); const statics = require('./statics'); const utils = require('./utils'); const codeParser = require('./code-parser'); let globalOptions = {}; function setOptions(options) { globalOptions = options; } const overwriteMerge = (destinationArray, sourceArray, options) => { if (destinationArray || sourceArray || options) return sourceArray; }; /** * Recognize and handle a file and its endpoints. * @param {string} filePath file's path. * @param {string} pathRoute Route's path to which file endpoints belong. * @param {string} relativePath Relative file's path. * @param {array} receivedRouteMiddlewares Array containing middleware to be applied in the endpoint's file. */ function readEndpointFile(filePath, pathRoute = '', relativePath, receivedRouteMiddlewares = [], restrictedContent, globalSwaggerProperties) { return new Promise(resolve => { let paths = {}; fs.readFile(filePath, 'utf8', async function (err, data) { if (err || !data || data.trim() === '') { return resolve(false); } /** * Experimental parser. Used to get variables * In the future, will be used to get other patterns */ let jsParsed = codeParser.jsParser(await handleData.removeComments(data, true)); // If 'jsParsed' == null, try to get only the variables if (!jsParsed) { jsParsed = await codeParser.jsParserEsModule(data); } let keywords = ['route', 'use', ...statics.METHODS]; let regex = ''; keywords.forEach(word => (regex += '\\s*\\n*\\t*\\.\\s*\\n*\\t*' + word + '\\s*\\n*\\t*\\(|')); regex = regex.replace(/\|$/, ''); /** * dataToGetPatterns: this variable will be used to get * patterns before of method, such as: app, route, etc. */ let dataToGetPatterns = data; // dataToGetPatterns = 'data' without strings, comments and inside parentheses dataToGetPatterns = await handleData.removeComments(dataToGetPatterns, false); dataToGetPatterns = await handleData.removeStrings(dataToGetPatterns); dataToGetPatterns = await handleData.removeInsideParentheses(dataToGetPatterns, true); /** * Bugfix when HTTP methods are at the end of 'dataToGetPatterns' * Issue: #49 */ if (dataToGetPatterns) { let lastElem = dataToGetPatterns.split('.').slice(-1)[0].replaceAll(' ', ''); if (statics.METHODS.includes(lastElem)) { dataToGetPatterns = dataToGetPatterns + '('; } } /* END CASE */ let firstPattern = null; let patternsServer = []; // Stores patterns, such as: route, app, etc... let propRoutes = []; // Used to store the new Router() properties, such as 'prefix' let regexRouteMiddlewares = ''; let dataSrc = null; /** * CASE: * import UserRouters from "./user"; * ... * router.use("/", new UserRouters().routes); */ restrictedContent ? (dataSrc = restrictedContent) : (dataSrc = data); let aData = await handleData.removeComments(dataSrc, true); aData = await handleData.clearData(aData); let converted = await handleData.dataConverter(aData); aData = converted.data; patternsServer = converted.patterns; /** * Eliminating unwanted patterns within endpoints * Avoinding cases, such as: route.get('/path', ... ...query().delete().where(...); whithin of the endpoint's functions make problems because of the '.delete()' */ let aDataAux = aData; let finished = false; let aDataToClean = new Set(); let count = 0; while (!finished && count < 300) { count += 1; // To avoid infinite loop let dat = await utils.stack0SymbolRecognizer(aDataAux, '(', ')'); if (dat == null) { finished = true; continue; } aDataToClean.add(dat); dat = '(' + dat + ')'; aDataAux = aDataAux.replace(dat, ' '); } aDataToClean = [...aDataToClean]; // converting to array for (let idxData = 0; idxData < aDataToClean.length; ++idxData) { let data = aDataToClean[idxData]; let swaggerComments = await handleData.getSwaggerComments(data); data = await handleData.removeComments(data); // Avoiding ploblems when functions has the same name of a .methods for (let idxMet = 0; idxMet < statics.METHODS.length; ++idxMet) { let method = statics.METHODS[idxMet]; data = data.split(new RegExp('\\.\\s*\\n*\\t*' + method)); data = data.join('.{_{__function__}_}' + method); } data = '(' + data + (swaggerComments !== '' ? '\n' + swaggerComments : '') + ')'; aData = aData.replaceAll('(' + aDataToClean[idxData] + ')', data); } /** * CASE: const router = new Router({ prefix: '/api/v1' }); */ const regexNewRouter = /(\w*\s*\n*\t*=\s*\n*\t*new\s*\n*\t*Router\s*\n*\t*\(\s*\n*\t*{)/; if (regexNewRouter.test(aData)) { const routes = aData.split(regexNewRouter); for (let index = 1; index < routes.length; index += 2) { let route = routes[index]; let prop = routes[index + 1]; let routerObj = { routeName: null }; if (route.includes('Router') && prop.includes('prefix')) { routerObj.routeName = route.split(new RegExp('\\=|\\s|\\n|\\t'))[0].replaceAll(' ', ''); let prefix = prop; prefix = prefix.split(/\}\s*\n*\t*\)/)[0]; if (prefix && prefix.split(new RegExp('\\s*prefix\\s*\\n*\\t*\\:').length > 1)) { prefix = prefix.split(new RegExp('\\s*prefix\\s*\\n*\\t*\\:\\s*\\n*\\t*'))[1].trimLeft(); prefix = prefix.split(new RegExp('\\s|\\n|\\t|\\,'))[0].trim(); prefix = prefix.replaceAll("'", '').replaceAll('"', '').replaceAll('`', ''); } routerObj.prefix = prefix; propRoutes.push(routerObj); } } } /* END CASE */ if (aData.includes(statics.SWAGGER_TAG + '.patterns')) { /** * Manual pattern recognition * NOTE: Deprecated */ let patterns = new Set(patternsServer); try { patterns = eval( aData .replaceAll(' ', '') .split(statics.SWAGGER_TAG + '.patterns=')[1] .split('*/')[0] ); } catch (err) { console.error('Syntax error: ' + statics.SWAGGER_TAG + '.patterns' + aData.split(statics.SWAGGER_TAG + '.patterns')[1].split('*/')[0]); console.error(err); return resolve(false); } if (patterns.size > 0) { patterns.add('____CHAINED____'); // CASE: router.get(...).post(...).put(...)... } regex = ''; patterns.forEach(pattern => { if (pattern && pattern.split(new RegExp('\\!|\\=|\\<|\\>|\\,|\\;|\\:|\\{|\\}|\\(|\\)|\\[|\\]|axios|superagent|request|fetch|supertest', 'i')).length > 1) { return; } if (!firstPattern) { firstPattern = pattern; } let keywords = [...statics.METHODS]; keywords.forEach(word => (regex += `(\\s|\\n|\\t|;|\\*\\/)${pattern}\\s*\\n*\\t*\\.\\s*\\n*\\t*` + word + '\\s*\\n*\\t*\\(|')); regexRouteMiddlewares += `\\/?\\s*\\n*\\t*${pattern}\\s*\\n*\\t*\\.\\s*\\n*\\t*use\\s*\\n*\\t*\\(|`; }); regex = regex.replace(/\|$/, ''); regexRouteMiddlewares = regexRouteMiddlewares.slice(0, -1); patternsServer = [...patterns]; } else { /** * Automatic pattern recognition */ let serverVars = []; let patterns = new Set(patternsServer); serverVars = dataToGetPatterns.split(new RegExp(regex)); if (serverVars && serverVars.length > 1) serverVars.forEach(pattern => { let auxPattern = pattern .split(new RegExp(regex))[0] .split(/\n|\s|\t|;|\{|\}|\(|\)|\[|\]/) .splice(-1)[0]; // e.g.: app, route, server, etc. if (auxPattern && auxPattern != '') { auxPattern = auxPattern.split(/;|,|\{|\}|\(|\)|\[|\]| /).join(''); patterns.add(auxPattern); } }); if (patterns.size > 0) patterns.add('____CHAINED____'); // CASE: router.get(...).post(...).put(...)... regex = ''; patterns.forEach(pattern => { if (pattern.split(new RegExp('\\!|\\=|\\<|\\>|\\,|\\;|\\:|\\{|\\}|\\(|\\)|\\[|\\]|axios|superagent|request|fetch|supertest', 'i')).length > 1) { return; } if (!firstPattern) { firstPattern = pattern; } let keywords = [...statics.METHODS, 'route']; keywords.forEach(word => (regex += `(\\s|\\n|\\t|;|\\*\\/)${pattern}\\s*\\n*\\t*\\.\\s*\\n*\\t*` + word + '\\s*\\n*\\t*\\(|')); regexRouteMiddlewares += `(\\/?\\s*\\n*\\t*${pattern}\\s*\\n*\\t*\\.\\s*\\n*\\t*use\\s*\\n*\\t*\\(\\s*\\n*\\t*)|`; }); regex = regex.replace(/\|$/, ''); regexRouteMiddlewares = regexRouteMiddlewares.slice(0, -1); patternsServer = [...patterns]; } let aForcedsEndpoints = swaggerTags.getForcedEndpoints(aData, { filePath }); aForcedsEndpoints = aForcedsEndpoints.map(forced => { return (forced += '\n' + statics.STRING_BREAKER + 'FORCED' + statics.STRING_BREAKER + '\n'); }); /** * routeMiddlewares: This will cause the middleware to be passed on to all sub-routes */ let routeMiddlewares = [ ...receivedRouteMiddlewares.map(r => { r.path = false; r.fixedRoute = true; r.bytePosition = -1; return r; }) ]; /** * CASE: router.use(middleware).get(...).post(...).put(...)... * REFACTOR: pass to function */ let rawRouteMiddlewares = aData.split(new RegExp(regexRouteMiddlewares)); rawRouteMiddlewares.shift(); let localRouteMiddlewares = []; // localRouteMiddlewares: Used to store and to apply middleware's route in the local endpoints aData = await handleData.addReferenceToMethods(aData, patternsServer); /** * CASE: * router.all('/...', ...) */ aData = aData.split(new RegExp('\\.\\s*all\\s*\\(\\[\\_\\[all\\]\\_\\]\\)\\(\\[\\_\\[')); aData = aData.join('.use([_[use]_])([_['); /* END CASE */ const aDataRaw = aData; /** * Getting the reference of all files brought with 'import' and 'require' */ let importedFiles = null; let aRoutes = null; let routePrefix = ''; // prefix of new Router() if (restrictedContent) { restrictedContent = await handleData.removeComments(restrictedContent); importedFiles = await getImportedFiles(data, relativePath); } else { importedFiles = await getImportedFiles(aDataRaw, relativePath); } /** * Identifying "express.Router()" */ let expressVarName = []; if (importedFiles.length > 0) { let idx = importedFiles.findIndex(i => i.fileName === 'express'); if (idx > -1) { let varName = importedFiles[idx].varFileName; let varRouteFound = aData.replaceAll('\n', '').split(new RegExp(`(const|let|var)(\\s+\\w+\\s*\\t*\\=\\s*\\t*${varName}\\s*\\t*\\.\\s*\\t*Router\\s*\\t*\\(\\s*\\t*\\))`)); varRouteFound.map(d => { let midd = d.split(new RegExp(`\\=\\s*\\t*${varName}\\s*\\t*\\.\\s*\\t*Router\\s*\\t*\\(\\s*\\t*\\)`)); if (midd.length > 1) { expressVarName.push(midd[0].trim()); } }); let varExpressRouteFound = aData.replaceAll('\n', '').split(new RegExp(`(\\s+\\t*${varName}\\s*\\t*\\.\\s*\\t*Router\\s*\\t*\\(\\s*\\t*\\))`)); if (varExpressRouteFound[1] && varExpressRouteFound[2] && varExpressRouteFound[2].split(/^____CHAINED____/).length > 1) { let routerVar = 'router'; if (regex == '') { let keywords = [...statics.METHODS, 'route']; keywords.forEach(word => (regex += `(\\s|\\n|\\t|;|\\*\\/)${routerVar}\\s*\\n*\\t*\\.\\s*\\n*\\t*` + word + '\\s*\\n*\\t*\\(|')); keywords.forEach(word => (regex += `(\\s|\\n|\\t|;|\\*\\/)____CHAINED____\\s*\\n*\\t*\\.\\s*\\n*\\t*` + word + '\\s*\\n*\\t*\\(|')); regexRouteMiddlewares += `(\\/?\\s*\\n*\\t*${routerVar}\\s*\\n*\\t*\\.\\s*\\n*\\t*use\\s*\\n*\\t*\\(\\s*\\n*\\t*)|`; regex = regex.replace(/\|$/, ''); aData = aData.replaceAll(new RegExp(`\\s+\\t*${varName}\\s*\\t*\\.\\s*\\t*Router\\s*\\t*\\(\\s*\\t*\\)____CHAINED____`), ` ${routerVar}`); aData = await handleData.addReferenceToMethods(aData, [routerVar, '____CHAINED____']); } else { aData = aData.replaceAll(new RegExp(`\\s+\\t*${varName}\\s*\\t*\\.\\s*\\t*Router\\s*\\t*\\(\\s*\\t*\\)____CHAINED____`), ` ${routerVar}`); } } } } if (regex != '' || aForcedsEndpoints.length > 0) { if (regex == '' && aForcedsEndpoints.length > 0) { aData = [...aForcedsEndpoints]; } else { aData = '\n' + aData; aData = aData.replaceAll(new RegExp('____CHAINED____'), ' ____CHAINED____'); aData = aData.replaceAll(new RegExp('\\[ ____CHAINED____'), '[____CHAINED____'); aData = [...aData.split(new RegExp(regex)), ...aForcedsEndpoints]; aData[0] = undefined; // Delete 'header' } aData = aData.filter(data => { if (data && data.replaceAll('\n', '').replaceAll(' ', '').replaceAll('\t', '') != '') { return true; } return false; }); let aDataRawCleaned = await handleData.removeComments(aDataRaw, true); aDataRawCleaned = aDataRawCleaned.replaceAll('\n', ' '); aRoutes = aDataRawCleaned.split(new RegExp(`\\s*\\t*\\w\\s*\\t*\\.\\s*\\t*use\\s*\\t*\\(`)); if (aRoutes.length > 1) { aRoutes.shift(); } aData = [...aRoutes, ...aData]; let lastValidPattern = ''; /** * All endpoints will be processed here */ for (let idxElem = 0; idxElem < aData.length; idxElem++) { let elem = aData[idxElem]; if (!elem || elem.slice(0, 3) !== '[_[') { continue; } let endpointFunctions = []; let rawPath = codeParser.getUntil(elem, ','); let bytePosition = null; if (rawPath.split(']_])(')[2]) { bytePosition = parseInt(rawPath.split(']_])(')[2].split('[_[')[1]); rawPath = rawPath.split('_])(')[3]; if ((rawPath && rawPath.includes(')') && !rawPath.split(')')[0].includes('(')) || rawPath == data) { // has no path rawPath = false; } } // Middleware without path. TODO: handle arrow function if (rawPath && rawPath.split(new RegExp('\\s*function\\s*\\(')).length > 1) { rawPath = ''; } let rawPathResolved = rawPath ? rawPath.replaceAll(' ', '') : false; rawPathResolved = await codeParser.resolvePathVariables(rawPathResolved, bytePosition, jsParsed, importedFiles); let endpointSwaggers = null; let objEndpoint = {}; let path = false; let method = false; let predefMethod = false; let req = null; let res = null; let autoMode = true; let objParameters = {}; let objResponses = {}; let forced = false; let predefPattern = false; let isChained = false; if (elem && elem.includes('[_[') && elem.includes(']_]')) { elem = elem.split(new RegExp('\\[_\\[|\\]_\\]\\)\\(')); predefMethod = elem[1]; predefPattern = elem[3]; bytePosition = parseInt(elem[5]); if (predefPattern === '____CHAINED____') { // CASE: router.get(...).post(...).put(...)... predefPattern = lastValidPattern; isChained = true; } else { lastValidPattern = predefPattern; } // CASE: router.use(middleware).get(...).post(...).put(...)... if (elem[6] && elem[6].includes('____CHAINED____')) { let midd = elem[6].split('____CHAINED____')[0]; localRouteMiddlewares.push({ middleware: midd, rawRoute: aData[idxElem].split(midd)[1] }); continue; } let prefixFound = propRoutes.find(r => r.routeName === predefPattern); if (prefixFound) { routePrefix = prefixFound.prefix || ''; } else { routePrefix = ''; } if (elem.length < 5) { // Forced Endpoint let found = elem.find(e => e.includes('/_undefined_path_0x')); if (found) { elem = found; endpointFunctions.push({ metadata: null, callbackParameters: null, func: found }); } else { continue; } } else { elem = elem[6]; } elem = elem.trim(); if (elem.includes('#swagger.path')) { rawPath = swaggerTags.getPath(elem, autoMode); } } elem = await utils.stackSymbolRecognizer(elem, '(', ')'); /** * CASE (continuing): router.use(middleware).get(...).post(...).put(...)... * Adding middleware to be processed together with the other endpoint functions */ if (isChained) { const endpointRegex = `\\(\\[\\_\\[${predefMethod}\\]\\_\\]\\)\\(\\[\\_\\[____CHAINED____\\]\\_\\]\\)\\(\\[\\_\\[${bytePosition}\\]\\_\\]\\)\\(\\s*\\n*\\t*${rawPath}\\s*\\n*\\t*\\,`; const found = localRouteMiddlewares.find(midd => midd.rawRoute && midd.rawRoute.split(new RegExp(endpointRegex)).length > 1); if (found) { elem += ',' + found.middleware; } } if (elem.includes(statics.STRING_BREAKER + 'FORCED' + statics.STRING_BREAKER)) { forced = true; } if (swaggerTags.getIgnoreTag(elem)) { continue; } else if (swaggerTags.getIgnoreTag(globalSwaggerProperties) && elem && elem.split(new RegExp(`${statics.SWAGGER_TAG}.ignore\\s*\\=`)).length == 1) { continue; } autoMode = swaggerTags.getAutoTag(elem); const elemOrig = elem; /** * Handling passed functions in the endpoint parameter, such as: app.get("/path", ...) */ let elemParam = await handleData.removeStrings(elem); elemParam = await handleData.removeComments(elemParam); if ((elemParam && elemParam.split(',').length > 1 && !forced) || predefMethod === 'use') { let functions = []; // Array that contains possible functions in other files let auxElem = await handleData.removeComments(elem); auxElem = auxElem.replace(rawPath, ''); let functionsInParameters = auxElem; if (functionsInParameters.slice(-1)[0] == ')') { // REFACTOR: use regex functionsInParameters = functionsInParameters.slice(0, -1); } functionsInParameters = functionsInParameters.split(','); auxElem = auxElem.replaceAll('\n', '').replaceAll(' ', ''); if (auxElem.split(',').length > 1 || predefMethod === 'use') { let found = expressVarName.findIndex(v => auxElem.split(new RegExp(`\\,\\s*\\n*\\t*${v}\\s*\\n*\\t*\\)`)).length > 1); if (expressVarName !== '' && found > -1) { let pathExpressRouter = utils.popString(rawPath); if (pathExpressRouter && pathExpressRouter.length > 0) { let routerObj = { routeName: null }; routerObj.routeName = expressVarName[found]; routerObj.prefix = pathExpressRouter; propRoutes.push(routerObj); delete expressVarName[found]; // to consider only the first statement continue; } } /** * Handling foo.method('/path', ..., ...)' * Getting function not referenced ( such as: (req, res) => { ... } ) */ let functionsStr = elemOrig.replace(rawPath, '').split(','); if (functionsStr.length > 1 && rawPath) { functionsStr.shift(); } functionsStr = functionsStr.join(','); functionsStr = functionsStr.split(new RegExp('^\\s*function\\s*\\(')); functionsStr = functionsStr.join('('); functionsStr = functionsStr.split(new RegExp('\\,\\s*function\\s*\\(')); functionsStr = functionsStr.join('( ('); functionsStr = functionsStr.replaceAll('{_{__function__}_}', ''); for (let idxFunc = 0; idxFunc < 15; ++idxFunc) { // Adding '(' and ')' to arrow functions that not contains '(' and ')', such as: async req => { if (functionsStr && functionsStr.split(new RegExp('\\s*\\t*=>\\s*\\n*\\t*').length > 1)) { let params = functionsStr.trim().split(new RegExp('\\s*\\t*=>\\s*\\n*\\t*')); if (params && params.length > 1 && params[0].trim().slice(-1)[0] !== ')') { let paramsAux = params[0].split(new RegExp('\\s+|\\n+|\\t+|\\,|\\.|\\;|\\:')); paramsAux = paramsAux.slice(-1)[0]; if (paramsAux.split(/\*|\\|\/|\(|\)|\{|\}|\[|\]/).length === 1 && paramsAux !== '') functionsStr = functionsStr.replace(new RegExp(`${paramsAux}\\s*\\t*=>\\s*\\n*\\t*`), `(${paramsAux}) => `); } } let funcNotReferenced = await handleData.popFunction(functionsStr); if (predefMethod == 'use' && funcNotReferenced) { if (funcNotReferenced.split(')')[0].split(',').length > 2) { let isLocalRouteMiddleware = false; if (aData[idxElem].split(new RegExp(regex)).length > 1) { // Verify if is not a local route middleware, such as: route.use(middleware).get(...).post(...)... isLocalRouteMiddleware = true; } routeMiddlewares.push({ metadata: null, callbackParameters: null, func: funcNotReferenced, middleware: true, path: rawPathResolved === '' ? false : rawPathResolved, isLocalRouteMiddleware, bytePosition }); functionsStr = functionsStr.replace(funcNotReferenced, ' '); } } else if (funcNotReferenced) { /** * CASE: * app.method("/foo", (req, res) => { * foo(req, res); * }); */ if (funcNotReferenced.trim()[0] == '(') { // there are parameters let funcNotRefFormated = funcNotReferenced.replaceAll('(', '( ').replaceAll(')', ' )'); let funcParams = await utils.stack0SymbolRecognizer(funcNotRefFormated, '(', ')'); let regexParams = ''; if (funcParams) { funcParams = funcParams.split(new RegExp('\\:\\s*\\n*\\t*Request\\s*\\n*\\t*|\\:\\s*\\n*\\t*Response\\s*\\n*\\t*|\\:\\s*\\n*\\t*Next\\s*\\n*\\t*|\\:\\s*\\n*\\t*any\\s*\\n*\\t*', 'i')); let tsFunction = false; if (funcParams.length > 1) { tsFunction = true; } funcParams = funcParams.join('').replaceAll('\n', '').replaceAll(' ', '').split(','); let numParams = funcParams.length; for (let idx = 0; idx < numParams; ++idx) { regexParams += `\\([\\w|\\s]*\\,?[\\w|\\s]*\\,?[\\w|\\s]*[\\s|\\,]+${funcParams[idx]}[\\s|\\,]+[\\w|\\s]*\\,?[\\w|\\s]*\\,?[\\w|\\s]*\\)|`; } regexParams = regexParams.slice(0, -1); let refFunc = null; if (funcNotRefFormated) { refFunc = funcNotRefFormated.split(new RegExp(regexParams)); } if (refFunc && refFunc.length > 1) { if (tsFunction) { refFunc = refFunc.slice(0, -1); } else { refFunc = refFunc.slice(1, -1); } refFunc.forEach(f => { let func = f.replaceAll('\n', ' ').split(new RegExp('\\s*\\t*\\.\\s*\\t*')); func = func.join('.'); func = func.trim().split(new RegExp('\\s|\\n|\\t|\\;|\\/|\\,')); func = func.slice(-1)[0].trim(); if (!statics.RESERVED_FUNCTIONS.includes(func)) { // TODO: improve this? functions.push(func); } }); } } } /* END CASE */ if (functionsStr && functionsStr.split(funcNotReferenced).length > 1) { functionsStr = functionsStr.replace(funcNotReferenced, ' '); } else { let params = await utils.stack0SymbolRecognizer(funcNotReferenced, '(', ')'); if (params && functionsStr.split('(' + params + ')').length > 1) { functionsStr = functionsStr.replace('(' + params + ')', ' '); } else { // TODO: verify this case } } if (funcNotReferenced.includes('(') && funcNotReferenced.includes(')')) { endpointFunctions.push({ metadata: null, callbackParameters: null, func: funcNotReferenced }); } } else { break; } } // endpointSwaggers: Keep 'global' #swaggers in the endpoints, such as: foo.get('/path', /* #swagger.description = "..." */ functions...) endpointSwaggers = await handleData.getSwaggerComments(functionsStr); functionsStr = await handleData.removeComments(functionsStr); functions = [...functions, ...functionsStr.split(',')]; } /** * functions: Array that contains possible functions in other files */ for (let index = 0; index < functions.length; index++) { let func = functions[index]; if (!func) { continue; } let funcTest = func.replaceAll('\n', '').replaceAll('\t', '').replaceAll(' ', ''); if (funcTest == '' || funcTest == ')') { continue; } func = func.trim(); if (func.slice(0, 4) === 'new ') { func = func.slice(4); if (func.slice(-1)[0] == ')') { func = func.slice(0, -1); } if (func.includes('.')) { if (func.includes('(') && func.includes(')')) { func = func.split(new RegExp(`\\s*\\)\\s*\\.\\s*`)).join(`).`); func = func.split(/\(|\)/); func = func[0] + func.slice(-1)[0]; } } } let exportPath = null; let idx = null; let functionName = null; let varFileName = null; let refFuncInParamStr = null; // let refFuncInParam = []; const rexRequire = /\s*require\s*\n*\t*\(/; if (rexRequire.test(func)) { if (func && func.split(new RegExp('\\(\\s*__dirname\\s*\\+\\s*\\"?\\\'?\\`?')).length > 1) { func = func.replaceAll("'", '"').replaceAll('`', '"'); func = func.split(new RegExp('\\(\\s*__dirname\\s*\\+\\s*\\"')); func = func.join('(".'); } /** * CASE: foo.method('/path', require('./pathToFile.js')) */ exportPath = func.split(rexRequire); exportPath = exportPath.slice(-1)[0]; exportPath = exportPath.split(')')[0]; exportPath = await resolvePathFile(exportPath, relativePath); } else { func = func.replaceAll('\n', '').replaceAll('\t', '').replaceAll(' ', '').replaceAll('[', '').replaceAll(']', ''); /** * CASE: awilix-express * const fooFoo = require('./pathToFoo') * ... * router.method('/path', fooFoo('foo') ) */ if (func.includes('(') && func.includes(')')) { let params = await utils.stack0SymbolRecognizer(func, '(', ')'); // TODO: get array with all strings and try to find with each one if (params && (params[0] == '"' || params[0] == "'" || params[0] == '`')) { refFuncInParamStr = params.replaceAll('"', '').replaceAll("'", '').replaceAll('`', ''); } } /* END CASE */ /* // TODO: Verify this case if (func.includes('(') && func.includes(')')) { let params = await utils.stack0SymbolRecognizer(func, '(', ')'); // TODO: get array with all functions and try to find with each one if (params) { params.split(',').forEach( p => { refFuncInParam.push(p.replaceAll('"', '').replaceAll("'", '').replaceAll('`', '').replaceAll(' ', '')); }) } } */ func = func.split(new RegExp('\\(|\\)'))[0]; if (func.split(new RegExp('\\(|\\)|\\[|\\]|\\{|\\}|\\!|\\=|\\>|\\<')).length > 1 || func.trim() == '') { continue; } if (func.split('.').length > 1) { // Identifying subfunction reference, such as: 'controller.store' in the foo.get('/path', controller.store) functionName = func.split('.')[1].trim(); varFileName = func.split('.')[0].trim(); } else { varFileName = func.split('.')[0].trim(); } // First, tries to find in the import/require idx = importedFiles.findIndex(e => e.varFileName && varFileName && e.varFileName == varFileName); if (idx == -1) { // Second, tries to find in the 'exports' of import/require, such as 'foo' in the: import { foo } from './fooFile' for (let idxImp = 0; idxImp < importedFiles.length; ++idxImp) { let imp = importedFiles[idxImp]; if (exportPath) { break; } // First, try to find the 'alias' let found = imp && imp.exports ? imp.exports.find(e => e.varAlias && varFileName && e.varAlias == varFileName) : null; if (!found) { found = imp && imp.exports ? imp.exports.find(e => e.varName && varFileName && e.varName == varFileName) : null; } else { found.varName = found.varAlias; } if (found) { if (!functionName && !imp.isDirectory) { functionName = found.varName; } imp.isDirectory && found.path ? (exportPath = found.path) : (exportPath = imp.fileName); // TODO: change variable name if (exportPath && imp.isDirectory && (await utils.getExtension(exportPath)) == '' && (await utils.getExtension(exportPath + '/index')) != '') { exportPath = exportPath + '/index'; } } } if (exportPath) { if (exportPath.includes('../')) { exportPath = await resolvePathFile(exportPath, relativePath); } } } else { if (importedFiles[idx].isDirectory && !importedFiles[idx].isRequireDirLib) { exportPath = importedFiles[idx].fileName + '/index'; } } } // If found, so is a reference to another file if (idx > -1 || exportPath) { /** * Bringing reference */ let pathFile = null; if (exportPath) { pathFile = exportPath; } else { if (importedFiles[idx] && importedFiles[idx].isRequireDirLib && func && func.split('.').length == 3) { functionName = func.split('.')[2].trim(); pathFile = importedFiles[idx].fileName + '/' + func.split('.')[1].trim(); } else { pathFile = importedFiles[idx].fileName; } } let extension = await utils.getExtension(pathFile); let refFunction = await functionRecognizerInFile(pathFile + extension, functionName); // Trying to find the reference in the index file // TODO: implements to 'import' and 'exports.default' if (!refFunction && functionName && pathFile && pathFile.split('/').length > 1 && pathFile.split('/').slice(-1)[0] == 'index') { let dataIndexFile = await utils.getFileContent(pathFile + extension); if (dataIndexFile) { pathFile = pathFile.split('/').slice(0, -1).join('/'); // removing '/index' let auxPathFile = pathFile; let importsIndexFile = await getImportedFiles(dataIndexFile, pathFile); let idx = importsIndexFile.findIndex(e => e.varFileName && functionName && e.varFileName == functionName); pathFile = null; if (idx == -1) { importsIndexFile.forEach(imp => { if (pathFile) { return; } let found = imp && imp.exports ? imp.exports.find(e => e.varName && functionName && e.varName == functionName) : null; if (found) { if (!functionName) { functionName = found.varName; } if (imp.isDirectory) { pathFile = found.path; } else { pathFile = imp.fileName; // TODO: change variable name } } }); } else { pathFile = importsIndexFile[idx].fileName; } if (!pathFile) { // Try to find in "export * from ... " let exporteds = dataIndexFile.split(new RegExp(`export\\s*\\*\\s*from\\s*`)); if (exporteds.length > 1) { let found = exporteds.find(e => e.includes(`/${varFileName}`)); if (found) { let indexPath = utils.popString(found); if (indexPath) { if (indexPath.slice(0, 2) == './') { indexPath = indexPath.slice(2); } pathFile = auxPathFile + '/' + indexPath; } } } } if (pathFile) { extension = await utils.getExtension(pathFile); refFunction = await functionRecognizerInFile(pathFile + extension, functionName); } } } if (!refFunction && refFuncInParamStr) { let fileContent = await utils.getFileContent(pathFile + extension); if (fileContent && fileContent.includes('awilix-express')) refFunction = await functionRecognizerInFile(pathFile + extension, refFuncInParamStr); } /** * CASE: Reference to files in the index.ts * Ref.: issue #32 */ if (!refFunction) { if (!functionName) { let dataIndexFile = await utils.getFileContent(pathFile + extension); if (dataIndexFile) { pathFile = pathFile.split('/').slice(0, -1).join('/'); // removing '/index' /** * 'hidding' imports and catching only exports and * change exports to imports to catched by the getImportedFiles() */ dataIndexFile = dataIndexFile.split('import').join('__ignored__'); dataIndexFile = dataIndexFile.split('export').join('import'); let exportsIndexFile = await getImportedFiles(dataIndexFile, pathFile);