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

1,096 lines (976 loc) 90.5 kB
const swaggerTags = require('./swagger-tags'); const statics = require('./statics'); const tables = require('./tables'); const utils = require('./utils'); let globalOptions = {}; function setOptions(options) { globalOptions = options; } /** * Convert statements such as: "require('./path')(foo)" in "foo.use(require('./path'))" * Useful, because the statement "foo.use(require('./path'))" is already handled successfully. * @param {string} data file content */ function dataConverter(data) { return new Promise(resolve => { const origData = data; try { let patterns = new Set(); // CASE: Converting require("./foo")(app) to app.use(require("./foo")) if (!data) { return resolve({ data, patterns: [] }); } let founds = data.split(new RegExp('(require\\s*\\n*\\t*\\(.*\\)\\s*\\n*\\t*\\(\\s*\\n*\\t*.*\\s*\\n*\\t*\\))')); for (let idx = 0; idx < founds.length; ++idx) { let req = founds[idx]; if (req.split(new RegExp('^require')).length == 1) continue; if (founds[idx - 1] && founds[idx - 1].trim().slice(-1)[0] == '=') { // avoiding cases, such as: const foo = require(...)() continue; } req = req.split(new RegExp('require\\s*\\n*\\t*\\(|\\)\\s*\\n*\\t*\\(')); if (req && (req.length < 2 || !req[1].includes('./') || !req[2])) { continue; } req[2] = req[2].split(')')[0].trim(); req[2] = req[2].split(',')[0]; // TODO: verify which possition in req[2][0] is a route patterns.add(req[2]); let converted = `${req[2]}.use(require(${req[1]}))`; data = data.replace(founds[idx], converted); // TODO: use replaceAll() ? } return resolve({ data, patterns: [...patterns] }); } catch (err) { return resolve({ data: origData, patterns: [] }); } }); } /** * Removes unnecessary content. * @param {string} data file content. */ function clearData(data) { return new Promise(resolve => { if (!data) { return resolve(data); } const origData = data; try { // Change "// ..." comment to "/* ... */" const origData = data; try { data = data.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*headers\\s*\\n*\\t*\\[\\s*\\n*\\t*')).join('.headers['); data = data.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*header\\s*\\n*\\t*\\(\\s*\\n*\\t*')); if (data.length > 1) { for (let idxHeader = 1; idxHeader < data.length; ++idxHeader) { data[idxHeader] = data[idxHeader].replace(')', ']'); } data = data.join('.headers['); } else { data = data[0]; } data = data.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*header\\s*\\n*\\t*\\(\\s*\\n*\\t*')).join('.headers('); if (data.split('.headers[').length > 1) { data = data.split('.headers['); for (let idxHeaders = 1; idxHeaders < data.length; ++idxHeaders) { let d = data[idxHeaders]; if (d[0] === "'" || d[0] === '"' || d[0] === '`') { let str = utils.popString(d); d = d.replace(new RegExp(`.${str}.`), `${statics.STRING_QUOTE}${str}${statics.STRING_QUOTE}`); data[idxHeaders] = d; } } data = data.join('.headers['); } } catch (err) { data = origData; } data = data.replaceAll('\r', '\n'); data = data.replaceAll('\\r', '\n'); data = data.replaceAll('*//*', '*/\n/*'); data = data.replaceAll('*//*', '*/\n/*'); data = data.replaceAll('*///', '*/\n//'); data = data.replaceAll('///', '//'); data = data.replaceAll('://', ':/' + statics.STRING_BREAKER + '/'); // REFACTOR: improve this. Avoiding cases such as: ... http://... be handled as a comment data = data.split('//').map((e, idx) => { if (idx != 0) { return e.replace('\n', ' */ \n'); } return e; }); data = data.join('//').replaceAll('//', '/*'); data = data.replaceAll(':/' + statics.STRING_BREAKER + '/', '://'); let aData = data.replaceAll('\n', statics.STRING_BREAKER); aData = aData.replaceAll('\t', ' '); aData = aData.replaceAll(statics.STRING_BREAKER, '\n'); aData = aData.replaceAll(' async ', ' '); aData = aData.split(new RegExp('\\s*async\\s*\\(')); aData = aData.join(' ('); aData = aData.split(new RegExp('\\:\\s*async\\s*\\(')); aData = aData.join(': ('); aData = aData.split(new RegExp('axios\\s*\\n*\\t*\\.\\w*', 'i')); aData = aData.join('axios.method'); aData = aData.split(new RegExp('\\s*\\=\\s*\\n*\\t*asyncHandler\\s*\\n*\\t*\\(')); aData = aData.join(' = '); aData = aData.split(new RegExp('\\,\\s*\\n*\\t*expressAsyncHandler\\s*\\n*\\t*\\(')); aData = aData.join(' , '); return resolve(aData); } catch (err) { return resolve(origData); } }); } /** * Remove comments in a string. * @param {string} data file content. * @param {boolean} keepSwaggerTags keep comment with "#swagger.*". */ function removeComments(data, keepSwaggerTags = false) { return new Promise(resolve => { if (!data || data.length == 0) { return resolve(data); } let storedRegexs = []; let strToReturn = ''; let stackComment1 = 0; // For type // let stackComment2 = 0; // For type /* */ let buffer1 = ''; // For type // let buffer2 = ''; // For type /* */ // Won't remove comments in strings let isStr1 = 0; // " let isStr2 = 0; // ' let isStr3 = 0; // ` try { for (let idx = 0; idx < data.length; ++idx) { let c = data[idx]; if (stackComment1 == 0 && stackComment2 == 0) { // Type ' if (c == "'" && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 1) { isStr1 = 2; } else if (c == "'" && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 0 && isStr2 == 0 && isStr3 == 0) { isStr1 = 1; } // Type " else if (c == '"' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr2 == 1) { isStr2 = 2; } else if (c == '"' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 0 && isStr2 == 0 && isStr3 == 0) { isStr2 = 1; } // Type ` else if (c == '`' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr3 == 1) { isStr3 = 2; } else if (c == '`' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 0 && isStr2 == 0 && isStr3 == 0) { isStr3 = 1; } } // Type // if (c == '/' && data[idx + 1] == '/' && data[idx - 1] != '\\' && data[idx - 1] != ':' && stackComment1 == 0 && stackComment2 == 0) { // REFACTOR: improve this. Avoiding cases such as: ... http://... be handled as a comment stackComment1 = 1; } else if (c == '\n' && stackComment1 == 1) { stackComment1 = 2; } // Type /* */ else if (c == '/' && data[idx + 1] == '*' && data[idx - 1] != '\\' && stackComment1 == 0 && stackComment2 == 0 && isStr1 == 0 && isStr2 == 0 && isStr3 == 0) { stackComment2 = 1; } else if (c == '/' && data[idx - 1] == '*' && stackComment2 == 1 && isStr1 == 0 && isStr2 == 0 && isStr3 == 0 && buffer2 != '/*') { stackComment2 = 2; } /** * REGEX START */ if (c == '/' && data[idx - 1] != '\\' && data[idx + 1] != '/' && isStr1 == 0 && isStr2 == 0 && isStr3 == 0 && stackComment1 == 0 && stackComment2 == 0) { // Checking if it is valid regex let lidx = idx + 1; let lstr = ''; let regexStackParenthesis = 0; let regexStackSquareBracket = 0; while (lidx < data.length) { // prevent loop let lc = data[lidx]; if (lc == '(') { regexStackParenthesis += 1; } else if (lc == ')') { regexStackParenthesis -= 1; } else if (lc == '[') { regexStackSquareBracket += 1; } else if (lc == ']') { regexStackSquareBracket -= 1; } lstr += lc; lidx += 1; if (data[lidx] == '/' && data[idx - 1] != '\\' && regexStackParenthesis < 1 && regexStackSquareBracket < 1) { if (regexStackParenthesis < 0) { regexStackParenthesis = 0; } if (regexStackSquareBracket < 0) { regexStackSquareBracket = 0; } break; } } try { if (''.split(new RegExp(lstr)).length > -1) { // Valid regex const ref = `__¬¬¬__${idx}__¬¬¬__`; data = utils.replaceRange(data, idx, lidx + 1, ref); c = data[idx]; // Storing regex apart storedRegexs.push({ ref, regex: `/${lstr}/` }); } } catch (err) { // Invalid regex } } /* REGEX END */ if (isStr1 > 0 || isStr2 > 0 || isStr3 > 0 || (stackComment1 == 0 && stackComment2 == 0)) { strToReturn += c; } else if (stackComment1 == 1 || stackComment1 == 2) { // Keeps the comment being ignored. Like: // buffer1 += c; } else if (stackComment2 == 1 || stackComment2 == 2) { // Keeps the comment being ignored. Like: /* */ buffer2 += c; } if (stackComment1 == 2) { stackComment1 = 0; if (buffer1.includes('#swagger.') && keepSwaggerTags) { strToReturn += buffer1; // keeping the comment that has a swagger tag buffer1 = ''; } else buffer1 = ''; } if (stackComment2 == 2) { stackComment2 = 0; if (buffer2.includes('#swagger.') && keepSwaggerTags) { strToReturn += buffer2; // keeping the comment that has a swagger tag buffer2 = ''; } else buffer2 = ''; } if (isStr1 == 2) isStr1 = 0; if (isStr2 == 2) isStr2 = 0; if (isStr3 == 2) isStr3 = 0; if (idx == data.length - 1) { if (storedRegexs.length > 0) { storedRegexs.forEach(r => { strToReturn = strToReturn.replace(r.ref, r.regex); }); } strToReturn = strToReturn.replaceAll(' ', ' ').replaceAll(' ', ' ').replaceAll(' ', ' ').replaceAll(' ', ' '); return resolve(strToReturn); } } } catch (err) { if (storedRegexs.length > 0) { storedRegexs.forEach(r => { strToReturn = strToReturn.replace(r.ref, r.regex); }); } return resolve(strToReturn); } }); } /** * Return all "#swagger.*" in a string. * @param {string} data file content. */ function getSwaggerComments(data) { return new Promise(resolve => { if (data.length == 0) { return resolve(data); } let strToReturn = ''; let stackComment1 = 0; // For type // let stackComment2 = 0; // For type /* */ let buffer1 = ''; // For type // let buffer2 = ''; // For type /* */ // Won't remove comments in strings let isStr1 = 0; // " let isStr2 = 0; // ' let isStr3 = 0; // ` try { for (let idx = 0; idx < data.length; ++idx) { let c = data[idx]; if (stackComment1 == 0 && stackComment2 == 0) { // Type ' if (c == "'" && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 1) isStr1 = 2; if (c == "'" && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 0 && isStr2 == 0 && isStr3 == 0) isStr1 = 1; // Type " if (c == '"' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr2 == 1) isStr2 = 2; if (c == '"' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 0 && isStr2 == 0 && isStr3 == 0) isStr2 = 1; // Type ` if (c == '`' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr3 == 1) isStr3 = 2; if (c == '`' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 0 && isStr2 == 0 && isStr3 == 0) isStr3 = 1; } // Type // if (c == '/' && data[idx + 1] == '/' && stackComment1 == 0 && stackComment2 == 0) stackComment1 = 1; if (c == '\n' && stackComment1 == 1) stackComment1 = 2; // Type /* */ if (c == '/' && data[idx + 1] == '*' && stackComment1 == 0 && stackComment2 == 0) stackComment2 = 1; if (c == '/' && data[idx - 1] == '*' && stackComment2 == 1) stackComment2 = 2; if (stackComment1 == 1 || stackComment1 == 2) { // Keeps the comment being ignored. Like: // buffer1 += c; } else if (stackComment2 == 1 || stackComment2 == 2) { // Keeps the comment being ignored. Like: /* */ buffer2 += c; } if (stackComment1 == 2) { stackComment1 = 0; if (buffer1.includes('#swagger.')) { strToReturn += ' ' + buffer1; // keeping the comment that has a swagger tag buffer1 = ''; } else { buffer1 = ''; } } if (stackComment2 == 2) { stackComment2 = 0; if (buffer2.includes('#swagger.')) { strToReturn += ' ' + buffer2; // keeping the comment that has a swagger tag buffer2 = ''; } else { buffer2 = ''; } } if (isStr1 == 2) isStr1 = 0; if (isStr2 == 2) isStr2 = 0; if (isStr3 == 2) isStr3 = 0; if (idx == data.length - 1) { strToReturn = strToReturn.replaceAll(' ', ' ').replaceAll(' ', ' ').replaceAll(' ', ' ').replaceAll(' ', ' '); return resolve(strToReturn); } } } catch (err) { return resolve(strToReturn); } }); } /** * Remove all strings. * @param {string} data file content. */ function removeStrings(data) { return new Promise(resolve => { if (!data || data.length == 0) { return resolve(data); } let storedRegexs = []; let strToReturn = ''; let stackComment1 = 0; // For type // let stackComment2 = 0; // For type /* */ let buffer = ''; // For type /* */ // Won't remove comments in strings let isStr1 = 0; // " let isStr2 = 0; // ' let isStr3 = 0; // ` try { for (let idx = 0; idx < data.length; ++idx) { let c = data[idx]; // If in comments or regex, ignore strings if (stackComment1 == 0 && stackComment2 == 0) { // Type ' if (c == "'" && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 1) { isStr1 = 2; } else if (c == "'" && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 0 && isStr2 == 0 && isStr3 == 0) { isStr1 = 1; } // Type " else if (c == '"' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr2 == 1) { isStr2 = 2; } else if (c == '"' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 0 && isStr2 == 0 && isStr3 == 0) { isStr2 = 1; } // Type ` else if (c == '`' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr3 == 1) { isStr3 = 2; } else if (c == '`' && (data[idx - 1] != '\\' || (data[idx - 1] == '\\' && data[idx - 2] == '\\')) && isStr1 == 0 && isStr2 == 0 && isStr3 == 0) { isStr3 = 1; } } // Type // if (c == '/' && data[idx + 1] == '/' && data[idx - 1] != '\\' && data[idx - 1] != ':' && stackComment1 == 0 && stackComment2 == 0) { // REFACTOR: improve this. Avoiding cases such as: ... http://... be handled as a comment stackComment1 = 1; } else if (c == '\n' && stackComment1 == 1) { stackComment1 = 2; } // Type /* */ else if (c == '/' && data[idx + 1] == '*' && data[idx - 1] != '\\' && stackComment1 == 0 && stackComment2 == 0 && isStr1 == 0 && isStr2 == 0 && isStr3 == 0) { stackComment2 = 1; } else if (c == '/' && data[idx - 1] == '*' && stackComment2 == 1 && isStr1 == 0 && isStr2 == 0 && isStr3 == 0 && buffer != '/*') { stackComment2 = 2; } /** * REGEX START */ if (c == '/' && data[idx - 1] != '\\' && data[idx + 1] != '/' && isStr1 == 0 && isStr2 == 0 && isStr3 == 0 && stackComment1 == 0 && stackComment2 == 0) { // Checking if it is valid regex let lidx = idx + 1; let lstr = ''; let regexStackParenthesis = 0; let regexStackSquareBracket = 0; while (lidx < data.length) { let lc = data[lidx]; if (lc == '(') { regexStackParenthesis += 1; } else if (lc == ')') { regexStackParenthesis -= 1; } else if (lc == '[') { regexStackSquareBracket += 1; } else if (lc == ']') { regexStackSquareBracket -= 1; } lstr += lc; lidx += 1; if (data[lidx] == '/' && data[idx - 1] != '\\' && regexStackParenthesis < 1 && regexStackSquareBracket < 1) { if (regexStackParenthesis < 0) { regexStackParenthesis = 0; } if (regexStackSquareBracket < 0) { regexStackSquareBracket = 0; } break; } } try { if (''.split(new RegExp(lstr)).length > -1) { // Valid regex const ref = `__¬¬¬__${idx}__¬¬¬__`; data = utils.replaceRange(data, idx, lidx + 1, ref); c = data[idx]; // Storing regex apart storedRegexs.push({ ref, regex: `/${lstr}/` }); } } catch (err) { // Invalid regex } } /* REGEX END */ if (isStr1 == 0 && isStr2 == 0 && isStr3 == 0) { strToReturn += c; } else if (stackComment2 == 1 || stackComment2 == 2) { buffer += c; } if (stackComment1 == 2) { stackComment1 = 0; } if (stackComment2 == 2) { stackComment2 = 0; } if (isStr1 == 2) isStr1 = 0; if (isStr2 == 2) isStr2 = 0; if (isStr3 == 2) isStr3 = 0; if (idx == data.length - 1) { if (storedRegexs.length > 0) { storedRegexs.forEach(r => { strToReturn = strToReturn.replace(r.ref, r.regex); }); } strToReturn = strToReturn.replaceAll(' ', ' ').replaceAll(' ', ' ').replaceAll(' ', ' ').replaceAll(' ', ' '); // TODO: Verifiy return resolve(strToReturn); } } } catch (err) { if (storedRegexs.length > 0) { storedRegexs.forEach(r => { strToReturn = strToReturn.replace(r.ref, r.regex); }); } return resolve(strToReturn); } }); } /** * Remove all content in parentheses. * @param {string} data file content. * @param {boolean} keepParentheses if true, keep the parentheses "()" after erasing the contents inside. * @param {number} level remove according to stack level */ function removeInsideParentheses(data, keepParentheses = false, level = 0) { return new Promise(resolve => { if (data.length == 0) { return resolve(data); } let storedRegexs = []; let strToReturn = ''; let stack = 0; let buffer = ''; try { for (let idx = 0; idx < data.length; ++idx) { let c = data[idx]; /** * REGEX START */ if (c == '/' && data[idx - 1] != '\\' && data[idx + 1] != '/' && stack < 1) { // Checking if it is valid regex let lidx = idx + 1; let lstr = ''; let regexStackParenthesis = 0; let regexStackSquareBracket = 0; while (lidx < data.length) { // prevent loop let lc = data[lidx]; if (lc == '(') { regexStackParenthesis += 1; } else if (lc == ')') { regexStackParenthesis -= 1; } else if (lc == '[') { regexStackSquareBracket += 1; } else if (lc == ']') { regexStackSquareBracket -= 1; } lstr += lc; lidx += 1; if (data[lidx] == '/' && data[idx - 1] != '\\' && regexStackParenthesis < 1 && regexStackSquareBracket < 1) { if (regexStackParenthesis < 0) { regexStackParenthesis = 0; } if (regexStackSquareBracket < 0) { regexStackSquareBracket = 0; } break; } } try { if (''.split(new RegExp(lstr)).length > -1) { // Valid regex const ref = `__¬¬¬__${idx}__¬¬¬__`; data = utils.replaceRange(data, idx, lidx + 1, ref); c = data[idx]; // Storing regex apart storedRegexs.push({ ref, regex: `/${lstr}/` }); } } catch (err) { // Invalid regex } } /* REGEX END */ if (c == '(' && data[idx - 1] != '\\') { stack += 1; if (keepParentheses) { buffer += '('; } } if (stack == level) { strToReturn += c; } if (stack == 1) { buffer += c; } if (c == ')' && data[idx - 1] != '\\') { stack -= 1; if (stack < 0) { stack = 0; } if (keepParentheses) { buffer += ')'; } if (stack == level) { let auxIdx = idx + 1; let validChar = null; while (validChar == null && auxIdx <= data.length) { if (data[auxIdx] != ' ' && data[auxIdx] != '\n' && data[auxIdx] != '\t') { validChar = data[auxIdx]; } auxIdx += 1; } if (validChar == '(') { /** * Recognize middlewares in parentheses * Issue: #67 */ strToReturn += buffer; } buffer = ''; } if (stack == level && keepParentheses) { strToReturn += '()'; } } if (idx == data.length - 1) { if (storedRegexs.length > 0) { storedRegexs.forEach(r => { strToReturn = strToReturn.replace(r.ref, r.regex); }); } return resolve(strToReturn); } } } catch (err) { if (storedRegexs.length > 0) { storedRegexs.forEach(r => { strToReturn = strToReturn.replace(r.ref, r.regex); }); } return resolve(strToReturn); } }); } /** * Add "([_[method]_])([_[foo]_])([_[index]_])" to all endpoints. This standardize each endpoint. * * 'method': get, post, put, etc. * * 'foo': app, route, etc. * * 'index': id * * @param {string} data file content. * @param {array} patterns array containing patterns recognized as: app, route, etc. */ function addReferenceToMethods(data, patterns) { return new Promise(resolve => { if (!data) { return resolve(data); } let auxData = data; let routeEndpoints = []; const origData = data; try { // CASE: router.route('/user').get(authorize, (req, res) => { let aDataRoute = auxData.split(new RegExp(`.*\\s*\\n*\\t*\\.\\s*\\n*\\t*route\\s*\\n*\\t*\\(`)); if (aDataRoute.length > 1) { for (let idx = 1; idx < aDataRoute.length; ++idx) { // CASE: app.get([_[get]_])('/automatic1/users/:id', (req, res) => { for (let mIdx = 0; mIdx < statics.METHODS.length; ++mIdx) { let method = statics.METHODS[mIdx]; let line = aDataRoute[idx].split(new RegExp(`\\)(\\s*|\\n*|\\t*)\\.\\s*\\n*\\t*${method}\\s*\\n*\\t*\\(`)); if (line.length === 3) { line[0] = line[0].split(')')[0]; // TODO: refactor this line[2] = line[2].split(new RegExp(`\\)(\\s*|\\n*|\\t*)\\.\\s*\\n*\\t*get\\s*\\n*\\t*\\(|` + `\\)(\\s*|\\n*|\\t*)\\.\\s*\\n*\\t*head\\s*\\n*\\t*\\(|` + `\\)(\\s*|\\n*|\\t*)\\.\\s*\\n*\\t*post\\s*\\n*\\t*\\(|` + `\\)(\\s*|\\n*|\\t*)\\.\\s*\\n*\\t*put\\s*\\n*\\t*\\(|` + `\\)(\\s*|\\n*|\\t*)\\.\\s*\\n*\\t*delete\\s*\\n*\\t*\\(|` + `\\)(\\s*|\\n*|\\t*)\\.\\s*\\n*\\t*patch\\s*\\n*\\t*\\(|` + `\\)(\\s*|\\n*|\\t*)\\.\\s*\\n*\\t*options\\s*\\n*\\t*\\(`))[0]; routeEndpoints.push((patterns[0] || '_app') + `.${method}(` + line[0] + ',' + line[2]); } } } auxData = aDataRoute[0] + routeEndpoints.join('\n'); } /** * CASE: router.get(...).post(...).put(...)... */ let regexChainedEndpoint = ''; for (let idxMethod = 0; idxMethod < statics.METHODS.length; ++idxMethod) { regexChainedEndpoint += `(\\)\\s*\\n*\\t*\\.\\s*\\n*\\t*${statics.METHODS[idxMethod]}\\s*\\n*\\t*\\()|`; } regexChainedEndpoint = regexChainedEndpoint.replace(/\|$/, ''); auxData = auxData.split(new RegExp(regexChainedEndpoint)); auxData = auxData.filter(d => d); for (let idx = 1; idx < auxData.length; idx += 2) { if (auxData[idx + 1] && auxData[idx + 1].split('/*')[0].includes('*/')) // Avoind modification in string of #swagger.description continue; auxData[idx] = auxData[idx].replace('.', '____CHAINED____.'); } auxData = auxData.join(''); // END CASE let methods = [...statics.METHODS, 'use', 'all']; for (let idx = 0; idx < methods.length; ++idx) { for (let idxPtn = 0; idxPtn < patterns.length; ++idxPtn) { let method = methods[idx]; let pattern = patterns[idxPtn]; let regexMethods = `${pattern}\\s*\\n*\\t*\\.\\s*\\n*\\t*${method}\\s*\\n*\\t*\\((?!\\[)`; auxData = auxData.split(new RegExp(regexMethods)); /** * Chained middlewares. For example: route.use(...).use(...).use(...) */ if (auxData && auxData.length > 1 && method == 'use') { for (let idxData = 1; idxData < auxData.length; ++idxData) { let chainedUse = auxData[idxData].split(new RegExp(`\\)\\s*\\n*\\t*\\.\\s*\\n*\\t*use\\s*\\n*\\t*\\(`)); if (chainedUse.length > 1) { auxData[idxData] = chainedUse.join(`) ${pattern}` + `.use([_[use]_])([_[${pattern}]_])(`); } } } auxData = auxData.join((pattern || '_app') + `.${method}([_[${method}]_])([_[${pattern}]_])(`); } if (idx == methods.length - 1) { /* Adding byte position */ auxData = auxData.split(']_])([_['); let bytePosition = auxData[0].split('([_[')[0].length; for (let idxPtn = 1; idxPtn < auxData.length; ++idxPtn) { try { let auxBytePosition = auxData[idxPtn].split(']_])(')[1].split('([_[')[0].length; auxData[idxPtn] = auxData[idxPtn].replace(']_])(', `]_])([_[${bytePosition}]_])(`); bytePosition += auxBytePosition; } catch (err) { if (auxData[idxPtn]) { auxData[idxPtn] = auxData[idxPtn].replace(']_])(', `]_])([_[${999999999999}]_])(`); } continue; } } auxData = auxData.join(']_])([_['); return resolve(auxData); } } } catch (err) { return resolve(origData); } }); } /** * TODO: fill * @param {*} elem * @param {*} request * @param {*} objParameters */ function getQueryIndirectly(elem, request, objParameters) { const origObjParameters = objParameters; try { for (let idx = 0; idx < request.length; ++idx) { let req = request[idx]; if (req && req.split(new RegExp('\\;|\\{|\\(|\\[|\\"|\\\'|\\`|\\}|\\)|\\]|\\:|\\,|\\*|\\!|\\|')).length == 1 && elem && elem.split(new RegExp(' .*?\\s*\\t*=\\s*\\t*' + req + '\\.\\s*\\t*query(\\s|\\n|;|\\t)', 'gmi').length > 1)) { let queryVars = []; let aQuerys = elem.split(new RegExp('\\s*\\t*=\\s*\\t*' + req + '\\.\\s*\\t*query(\\s|\\n|;|\\t)', 'i')); aQuerys = aQuerys.slice(0, -1); if (aQuerys.length > 0) { // get variables name for (let idx = 0; idx < aQuerys.length; idx++) { if (aQuerys[idx] && aQuerys[idx].replaceAll(' ', '') != '') { queryVars.push(aQuerys[idx].split(new RegExp('\\s*|\\t*')).slice(-1)[0]); } } if (queryVars.length > 0) { queryVars.forEach(query => { if (query && query.split(new RegExp('\\;|\\{|\\(|\\[|\\"|\\\'|\\`|\\}|\\)|\\]|\\:|\\,|\\*|\\!|\\|')).length == 1) { let varNames = elem.split(new RegExp(' ' + query + '\\.')).splice(1); varNames = varNames.map(v => (v = v.split(new RegExp('\\s|;|\\n|\\t'))[0])); varNames.forEach(name => { objParameters[name] = { name, in: 'query' }; }); } }); } } } } return objParameters; } catch (err) { return origObjParameters; } } /** * Recognize content of .status(...) method (ExpressJS). * @param {string} elem content. * @param {array} response array containing variables of response. * @param {object} objResponses */ async function getStatus(elem, response, objResponses, filePath = '') { const origObjResponses = objResponses; try { elem = elem.replaceAll(new RegExp(`\\s*\\.\\s*json\\s*\\(\\s*`), `.json( `); elem = elem.replaceAll(new RegExp(`\\s*\\.\\s*send\\s*\\(\\s*`), `.send( `); elem = elem.replaceAll(new RegExp(`\\s*\\.\\s*status\\s*\\(\\s*`), `.status( `); elem = elem.replaceAll(new RegExp(`\\s*\\.\\s*sendStatus\\s*\\(\\s*`), `.sendStatus( `); elem = elem.replaceAll(new RegExp(`\\s*\\.\\s*sendFile\\s*\\(\\s*`), `.sendFile( `); for (let idx = 0; idx < response.length; ++idx) { let res = response[idx]; elem = elem.split(new RegExp(`[\\(|\\)|\\{|\\}|\\[|\\]|\\/|\\|;|:|=|!|@|\\$|#|\\?|\\+|,|\\||&|\\t|\\n|\\*]${res}`)).join(` ${res}`, 'gm'); if (res && elem && elem.split(new RegExp(' ' + res + '\\s*\\n*\\t*\\.\\s*\\n*\\t*status\\s*\\(| ' + res + '\\s*\\n*\\t*\\.\\s*\\n*\\t*sendStatus\\s*\\(')).length > 1) { elem.split(new RegExp('\\s+' + res + '\\s*\\n*\\t*\\.\\s*\\n*\\t*status\\s*\\(|\\s+' + res + '\\s*\\n*\\t*\\.\\s*\\n*\\t*sendStatus\\s*\\(')) .splice(1) .forEach(async s => { let status = await utils.stackSymbolRecognizer(s, '(', ')'); status = status.replaceAll('(', '').replaceAll(')', '').replaceAll(' ', ''); if (status && !utils.isNumeric(status) && status.split(new RegExp('\\?|\\|\\||\\:')).length > 1) { /** * CASE: Handle status function (Express.js) with variables or multiple status code * Issue: #62 */ let auxStatus = status.split(new RegExp('\\?|\\|\\||\\:')); auxStatus.forEach(sts => { if (utils.isNumeric(sts) && !!objResponses[sts] === false) { objResponses[sts] = { description: tables.getHttpStatusDescription(sts, swaggerTags.getLanguage()) || '' }; } else if (utils.isNumeric(sts) && !!objResponses[sts] === true) { // concatenated with existing information objResponses[sts] = { description: tables.getHttpStatusDescription(sts, swaggerTags.getLanguage()) || '', ...objResponses[sts] }; } }); } else if (utils.isNumeric(status) && !!objResponses[status] === false) { objResponses[status] = { description: tables.getHttpStatusDescription(status, swaggerTags.getLanguage()) || '' }; } else if (utils.isNumeric(status) && !!objResponses[status] === true) { // concatenated with existing information objResponses[status] = { description: tables.getHttpStatusDescription(status, swaggerTags.getLanguage()) || '', ...objResponses[status] }; } if (status == 400) { let rule = /validateRules\.([a-zA-Z0-9_]+)/gm.exec(elem); objResponses[status].description += "|" objResponses[status].description += filePath objResponses[status].description += "|"; if (rule && rule.length >= 2) { objResponses[status].description += rule[1] } } }); } /** * Catching status code 200 when res.send(...) or res.json(...) */ if (res && elem && elem.split(new RegExp('\\s+' + res + '\\s*\\n*\\t*\\.\\s*\\n*\\t*send\\s*\\(|\\s+' + res + '\\s*\\n*\\t*\\.\\s*\\n*\\t*json\\s*\\(|\\s+' + res + '\\s*\\n*\\t*\\.\\s*\\n*\\t*sendFile\\s*\\(')).length > 1) { if (!!objResponses[200] === false) { objResponses[200] = { description: tables.getHttpStatusDescription(200, swaggerTags.getLanguage()) }; } else if (!!objResponses[200] === true) { // concatenated with existing information objResponses[200] = { description: tables.getHttpStatusDescription(200, swaggerTags.getLanguage()), ...objResponses[200] }; } } } return objResponses; } catch (err) { return origObjResponses; } } /** * Recognize content of .setHeader(...) method (ExpressJS). * @param {string} elem content. * @param {string} path endpoint's path. * @param {string} method * @param {array} response array containing variables of response. * @param {object} objEndpoint */ function getHeader(elem, path, method, response, objEndpoint) { const origObjEndpoint = objEndpoint; try { for (let idx = 0; idx < response.length; ++idx) { let res = response[idx]; elem = elem.split(new RegExp('application/xml', 'i')); elem = elem.join('application/xml'); if (res && elem && elem.split(new RegExp(res + '\\s*\\t*\\n*\\.\\s*\\t*\\n*setHeader\\s*\\t*\\n*\\(')).length > 1) { elem = elem.split(new RegExp(res + '\\s*\\t*\\n*\\.\\s*\\t*\\n*setHeader\\s*\\(')); if (elem.length > 1 && elem[1].toLocaleLowerCase().includes('content-type')) { let content = elem[1].split(',')[1]; content = utils.popString(content); objEndpoint[path][method].produces = [content]; } } } if (objEndpoint[path][method].produces && objEndpoint[path][method].produces.length == 0) { objEndpoint[path][method].produces = undefined; } return objEndpoint; } catch (err) { return origObjEndpoint; } } /** * Recognize header, query and body content automatically. * @param {string} elem content. * @param {array} request array containing variables of response. * @param {object} objParameters */ function getHeaderQueryBody(elem, request, objParameters) { const origObjParameters = objParameters; if (elem) { elem = elem.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*query\\s*\\n*\\t*\\.\\s*\\n*\\t*')).join('.query.'); elem = elem.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*body\\s*\\n*\\t*\\.\\s*\\n*\\t*')).join('.body.'); elem = elem.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*headers')).join('.headers'); elem = elem.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*query')).join('.query'); elem = elem.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*body')).join('.body'); elem = elem.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*headers\\s*\\n*\\t*\\.\\s*\\n*\\t*')).join('.headers.'); elem = elem.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*query\\s*\\n*\\t*[\\;|\\,|\\}]')).join('.query '); elem = elem.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*body\\s*\\n*\\t*[\\;|\\,|\\}]')).join('.body '); elem = elem.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*headers\\s*\\n*\\t*[\\;|\\,|\\}]')).join('.headers '); elem = elem.split(new RegExp('\\s*\\n*\\t*\\.\\s*\\n*\\t*')).join('.'); elem = elem.split(new RegExp('\\s*\\n*\\t*\\=\\s*\\n*\\t*')).join(' = '); } try { for (let idx = 0; idx < request.length; ++idx) { let req = request[idx]; if (req && req.split(/\(|\)|\{|\}|\[|\]|\/|\\|;|:|!|@|\$|#|=|\?|\+|,|\||&|\t|\n|"|'|`|\*/).length > 1) { continue; } elem = elem.split(new RegExp(`[\\(|\\)|\\{|\\}|\\[|\\]|\\/|\\|;|:|!|@|\\$|#|\\?|\\+|,|\\||&|\\t|\\n|\\*]${req}`)).join(` ${req}`, 'gm'); const origElem = elem; // storing strings elem = elem.replaceAll(statics.STRING_QUOTE, '"'); let str = utils.popString(elem, true); let count = 0; let storedStrings = []; while (str && count < 300) { if (str) { let ref = `___STRING_REF___${count}___`; elem = elem.replace(str, ref); storedStrings.push({ ref: `___STRING_REF___${count}___`, str }); str = utils.popString(elem, true); } count += 1; } try { let inputTypes = ['headers', 'query', 'body']; for (let idxInput = 0; idxInput < inputTypes.length; ++idxInput) { let inputType = inputTypes[idxInput]; // ES Mudule/TypeScript variable let splitedElemTS = elem.split(new RegExp(`(\\s+\\w+\\s*\\:\\s*\\w+\\s*\\=\\s*${req}\\s*\\.\\s*${inputType}(?!\\.))`)); for (let idxElem = 1; idxElem < splitedElemTS.length; idxElem += 2) { let e = splitedElemTS[idxElem]; if (e && e.includes(':') && e.includes('=') && e.includes(`.${inputType}`)) { let varName = e.split(':')[0].trim(); elem = elem.split(new RegExp(`[\\(|\\)|\\{|\\}|\\[|\\]|\\/|\\|;|:|!|@|\\$|#|\\?|\\+|,|\\||&|\\t|\\n|\\*]${varName}`)).join(` ${varName}`, 'gm'); elem = elem.replaceAll(varName, `${req}.${inputType}`); } } // CommonJS variable let splitedElem = elem.split(new RegExp(`(\\s+\\w+\\s*\\=\\s*${req}\\s*\\.\\s*${inputType}(?!\\.))`)); for (let idxElem = 1; idxElem < splitedElem.length; idxElem += 2) { let e = splitedElem[idxElem]; if (e && e.includes('=') && e.includes(`.${inputType}`)) { let varName = e.split('=')[0].trim(); elem = elem.split(new RegExp(`[\\(|\\)|\\{|\\}|\\[|\\]|\\/|\\|;|:|!|@|\\$|#|\\?|\\+|,|\\||&|\\t|\\n|\\*]${varName}`)).join(` ${varName}`, 'gm'); elem = elem.replaceAll(varName, `${req}.${inputType}`); } } } } catch (err) { elem = origElem; } // Coming back with the strings storedStrings.forEach(s => { elem = elem.replace(s.ref, s.str); }); elem = elem.replaceAll('"', statics.STRING_QUOTE); /** * Headers */ if (globalOptions.autoHeaders && req && elem && elem.split(' ' + req + '.headers.').length > 1) { elem.split(' ' + req + '.headers.') .splice(1) .forEach(p => { p = p.trim(); let name = p .split(/\)|\{|\}|\[|\]|\/|\\|;|:|!|@|\$|#|=|\?|\+|,|\||&|\t|\n| /)[0] .replaceAll(' ', '') .replaceAll('\r', ''); if (name && name.includes('.')) { name = name.split('.')[0]; } if (name && name.slice(-1)[0] == '(') { return; } name = name.replaceAll('...', ''); if (!!objParameters[name] === false || (objParameters[name] && objParameters[name].name !== name) || (objParameters[name] && objParameters[name].name === nam