UNPKG

@codeque/core

Version:

Multiline code search for every language. Structural code search for JavaScript, TypeScript, HTML and CSS

233 lines (195 loc) 7.26 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.textSearch = textSearch; var _utils = require("./utils"); const getMatchPosition = (match, fileContents) => { const start = fileContents.indexOf(match); const end = start + match.length; const fileLines = fileContents.split(/\n/); const matchLines = match.split(/\n/); const firstMatchLine = matchLines[0]; const lastMatchLine = matchLines[matchLines.length - 1]; let charsToFirstLineCounter = 0; const startLineIndex = fileLines.findIndex(line => { const newLineCharLen = 1; charsToFirstLineCounter += line.length + newLineCharLen; return charsToFirstLineCounter > start; }); const endLineIndex = startLineIndex + matchLines.length - 1; const startLineColumn = fileLines[startLineIndex].indexOf(firstMatchLine); const endLineColumn = fileLines[endLineIndex].indexOf(lastMatchLine) + lastMatchLine.length; return { start, end, loc: { start: { line: startLineIndex + 1, column: startLineColumn }, end: { line: endLineIndex + 1, column: endLineColumn } } }; }; const prepareQuery = (queryCode, caseInsensitive) => { const regExpFlags = 'gm' + (caseInsensitive ? 'i' : ''); if (queryCode.length === 0) { return null; } const isWildcardRegExp = /\$\$\$?m?/g; let parts = queryCode.split(/"/g).map(part => part.split(/'/g)).flat(1); parts = parts.map((part, idx) => { const isStringContent = idx % 2 === 1; const zipParts = parts => { let result = ''; for (let i = 0; i < parts.length; i++) { const currentPart = parts[i]; const nextPart = parts[i + 1] ?? ''; if ((currentPart.match(isWildcardRegExp) ?? []).length > 0 || (nextPart.match(isWildcardRegExp) ?? []).length > 0 || nextPart === '') { result += currentPart; } else { result += `${currentPart}(\\s)*`; } } return result; }; const partsOfPart = part.split(_utils.nonIdentifierOrKeyword).filter(str => str !== '').map(subStr => { const splitByWildcard3 = subStr.split(/(\$\$\$m?)/g); if (splitByWildcard3.length > 1) { return splitByWildcard3; } return subStr; }).flat(1).map(subStr => { const splitByWildcard2 = subStr.split(/(\$\$m?)/g); if (subStr !== '$$$' && subStr !== '$$$m' && splitByWildcard2.length > 1) { return splitByWildcard2; } return subStr; }).flat(1).map(subStr => { const splitByWildcard1 = subStr.split(/(\$)/g); if (subStr !== '$$$' && subStr !== '$$$m' && subStr !== '$$' && subStr !== '$$m' && splitByWildcard1.length > 1) { return splitByWildcard1; } return subStr; }).flat(1).filter(str => { if (!isStringContent) { return str.trim() !== ''; } //we want to keep spaces inside strings return true; }).map(subStr => { if (_utils.nonIdentifierOrKeyword.test(subStr) || subStr === '$') { const escaped = '\\' + subStr.split('').join('\\'); return escaped; } return subStr; }); let result = isStringContent ? partsOfPart.join('') : zipParts(partsOfPart); if (!isStringContent) { result = result.replace(/\s+/g, '(\\s)*').replace(/;/g, ';?'); } result = result.replace(/\$\$\$m/g, '([\\S\\s])+?').replace(/\$\$m/g, '([\\S\\s])*?').replace(/\$\$\$/g, '([\\S\\t ])+?').replace(/\$\$/g, '([\\S\\t ])*?'); return result; }); const query = new RegExp(parts.reduce((regexp, part, index) => { if (index % 2 === 1) { regexp += '(\\s)*'; } regexp += `("|')`; if (index % 2 === 0) { regexp += '(\\s)*'; } regexp += part; return regexp; }), regExpFlags); let shallowQuery = null; const queryStartsWithWildcard = queryCode.startsWith('$$'); const queryEndsWithWildcard = queryCode.endsWith('$$') || queryCode.endsWith('$$m'); if (queryStartsWithWildcard || queryEndsWithWildcard) { const queryWithoutWildcards = queryCode.replace(isWildcardRegExp, ' '); shallowQuery = queryWithoutWildcards.match(/\w+/g); } return { regexpQuery: query, shallowQuery }; }; function textSearch({ queryCodes, filePaths, caseInsensitive, getFileContent, onPartialResult, maxResultsLimit, hardStopFlag }) { const queries = queryCodes.map(queryCode => prepareQuery(queryCode, caseInsensitive)).filter(query => Boolean(query?.regexpQuery)); if (queries.length === 0) { return { matches: [], hints: [], errors: ['Empty Query'] }; } const searchErrors = []; const allMatches = []; for (const filePath of filePaths) { if (maxResultsLimit !== undefined && allMatches.length > maxResultsLimit || hardStopFlag?.stopSearch) { break; } try { const fileContent = getFileContent(filePath); for (const query of queries) { if (query.shallowQuery === null || query.shallowQuery.every(subShallowQuery => fileContent.includes(subShallowQuery))) { const matches = (fileContent.match(query.regexpQuery) ?? []).map( /** * Some matches contains white at start or end * We have get rid of them as they break further position calculation logic * They are not necessary, we cannot effectively highlight new line char * These chars are result of query preparation in some edge cases * It do not make sense to search for spaces, so we can remove it */ match => match.trim()); let contentToMatchPosition = fileContent; const transformedMatches = matches.map(match => { const matchPositionData = getMatchPosition(match, contentToMatchPosition); // replace match in content to properly find the same match in the source file // For multiline matches we have to keep new line chars in order to properly determine lines for subsequent matches const matchGhost = match.replace(/\S/g, ' '); // replace any non-white space with a space char contentToMatchPosition = contentToMatchPosition.replace(match, matchGhost); const [extendedCodeFrame, newStartLine] = (0, _utils.getExtendedCodeFrame)(matchPositionData, fileContent); return { ...matchPositionData, code: match, extendedCodeFrame: { code: extendedCodeFrame, startLine: matchPositionData.loc.start.line + newStartLine }, query: query.toString(), filePath, // TODO: improve text search types, they do not provide aliases aliases: { identifierAliasesMap: {}, nodesTreeAliasesMap: {}, stringAliasesMap: {} } }; }); if (onPartialResult && transformedMatches.length > 0) { onPartialResult(transformedMatches); } allMatches.push(...transformedMatches); } } } catch (e) { searchErrors.push(e.message); } } const result = { errors: searchErrors, matches: allMatches, hints: [] }; return result; }