UNPKG

@codeque/eslint-plugin

Version:

Create custom ESLint rules based on code samples. Utilizing CodeQue - structural code search engine.

238 lines (237 loc) 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createLintCode = void 0; const core_1 = require("@codeque/core"); const utils_1 = require("./utils"); const telemetry_1 = require("./telemetry"); const queriesCache = {}; let preparationTime = 0; let shallowSearchTime = 0; let preparingVisitorsTime = 0; let preparingQueriesTime = 0; let filteringFilePathsTime = 0; const searchTimeForQueries = {}; process.on('beforeExit', () => { const print = console.log; const shouldPrintMetric = process.env.CODEQUE_DEBUG === 'true'; if (shouldPrintMetric) { print('\nCodeQue debug metrics:\n'); print('preparationTime', preparationTime); print('shallowSearchTime', shallowSearchTime); print('preparingVisitorsTime', preparingVisitorsTime); print('preparingQueriesTime', preparingQueriesTime); print('filteringFilePathsTime', filteringFilePathsTime); print('searchTimeForQueries', searchTimeForQueries); print(''); print('Telemetry is', telemetry_1.telemetryDisabled ? 'disabled' : 'enabled'); } }); const telemetryReported = { problem: false, suggestion: false, layout: false, }; const createLintCode = (type) => ({ meta: { type: type, docs: { description: 'Lint anything based on code sample(s).', }, fixable: 'code', schema: [ { type: 'array', items: { type: 'object', properties: { mode: { type: 'string', }, query: { type: 'string', }, message: { type: 'string', }, caseInsensitive: { type: 'boolean', }, includeFiles: { type: 'array', items: { type: 'string', }, }, excludeFiles: { type: 'array', items: { type: 'string', }, }, }, }, }, ], }, create: function (context) { const telemetry = (0, telemetry_1.createTelemetryInstance)(); const prepStart = performance.now(); const parser = (0, utils_1.assertCompatibleParser)(context.parserPath); const settings = context.options[0]; if (!settings || settings.length === 0) { return {}; } const code = context.getSourceCode().text; const absoluteFilePath = context.getPhysicalFilename(); const root = context.getCwd(); const defaultCaseInsensitive = true; const queryCodes = settings.map(({ query }) => query); const searchModes = settings.map(({ mode }) => mode).filter(Boolean); if (queryCodes.includes(undefined) || queryCodes.includes(null)) { throw new Error('Each setting has to have at least query defined.'); } if (searchModes.includes('text')) { throw new Error('"Text" search mode is not supported.'); } const parserSettings = core_1.__internal.parserSettingsMap[utils_1.parserNamesMappingsToCodeQueInternal[parser]](); const startPreparingQueries = performance.now(); const queriesParseResult = settings.map(({ query, caseInsensitive: caseInsensitive_ }) => { const caseInsensitive = caseInsensitive_ !== null && caseInsensitive_ !== void 0 ? caseInsensitive_ : defaultCaseInsensitive; const cacheKey = `(${query});${caseInsensitive}`; const queryFromCache = queriesCache[cacheKey]; if (queryFromCache) { return [[queryFromCache], true]; } const parsedQuery = (0, core_1.parseQueries)([query], caseInsensitive, parserSettings); queriesCache[cacheKey] = parsedQuery[0][0]; return parsedQuery; }); const queriesNotParsedCorrectly = queriesParseResult.filter(([_, parseOk]) => !parseOk); if (queriesNotParsedCorrectly.length > 0) { throw new Error((0, utils_1.formatQueryParseErrors)(queriesNotParsedCorrectly)); } const parsedQueries = queriesParseResult.map(([[parsedQuery]]) => parsedQuery); const parsedQueriesWithSettings = parsedQueries.map((parsedQuery, idx) => { var _a, _b, _c, _d; return ({ parsedQuery: parsedQuery, mode: (_a = settings[idx].mode) !== null && _a !== void 0 ? _a : 'include', caseInsensitive: (_b = settings[idx].caseInsensitive) !== null && _b !== void 0 ? _b : defaultCaseInsensitive, message: (_c = settings[idx].message) !== null && _c !== void 0 ? _c : 'Restricted code pattern', includeFiles: settings[idx].includeFiles, excludeFiles: (_d = settings[idx].excludeFiles) !== null && _d !== void 0 ? _d : [], }); }); preparingQueriesTime += performance.now() - startPreparingQueries; const startFilteringFilePaths = performance.now(); const queriesWithSettingsMatchedFilePath = parsedQueriesWithSettings.filter(({ includeFiles, excludeFiles }) => { const matchedFilePath = (0, core_1.filterIncludeExclude)({ filesList: [absoluteFilePath], searchRoot: root, exclude: excludeFiles, include: includeFiles, }); return matchedFilePath.length === 1; }); filteringFilePathsTime += performance.now() - startFilteringFilePaths; const shallowStart = performance.now(); const queriesWithSettingsMatchedShallow = queriesWithSettingsMatchedFilePath.filter((parsedQuery) => { return core_1.__internal.shallowSearch({ queries: [parsedQuery.parsedQuery], fileContent: code, logger: {}, caseInsensitive: parsedQuery.caseInsensitive, }); }); shallowSearchTime += performance.now() - shallowStart; if (queriesWithSettingsMatchedShallow.length === 0) { return {}; } const preparingVisitorsStart = performance.now(); const queryCodesGroupedByStartingNode = queriesWithSettingsMatchedShallow.reduce((map, query) => { const nodeType = query.parsedQuery.queryNode.type; if (!map[nodeType]) { map[nodeType] = []; } map[nodeType].push(query); return map; }, {}); const createSearchForNode = (parsedQueries) => (node) => { for (const queryWithSettings of parsedQueries) { const searchOptions = { mode: queryWithSettings.mode, parserSettings, debug: false, caseInsensitive: queryWithSettings.caseInsensitive, }; const startSearch = performance.now(); const { isMultistatement, queryCode, queryNode } = queryWithSettings.parsedQuery; if (searchTimeForQueries[queryCode] === undefined) { searchTimeForQueries[queryCode] = 0; } const matchContext = core_1.__internal.createMatchContext(); const { match } = core_1.__internal.validateMatch(node, queryWithSettings.parsedQuery.queryNode, searchOptions, matchContext); if (match) { let matchData = core_1.__internal.getMatchFromNode(node, parserSettings, matchContext.getAllAliases()); if (isMultistatement) { /** * For multi-statement queries we search where exactly statements are located within parent node */ matchData = core_1.__internal.getLocationOfMultilineMatch(matchData, queryNode, searchOptions, core_1.__internal.traverseAndMatch); } searchTimeForQueries[queryCode] += performance.now() - startSearch; context.report({ loc: matchData.loc, message: queryWithSettings.message, }); } } }; const visitorsSearchArrayMap = Object.entries(queryCodesGroupedByStartingNode) .map(([nodeType, queries]) => { const visitorKeys = core_1.__internal.getVisitorKeysForQueryNodeType(nodeType, parserSettings); const searchForNode = createSearchForNode(queries); const objectEntries = visitorKeys.map((visitorKey) => [ visitorKey, searchForNode, ]); return Object.fromEntries(objectEntries); }) .reduce((visitorsMap, visitorObj) => { const newVisitorsMap = Object.assign({}, visitorsMap); for (const visitorKey in visitorObj) { const searchFn = visitorObj[visitorKey]; if (Array.isArray(newVisitorsMap[visitorKey])) { newVisitorsMap[visitorKey].push(searchFn); } else { newVisitorsMap[visitorKey] = [searchFn]; } } return newVisitorsMap; }, {}); const visitors = Object.fromEntries(Object.entries(visitorsSearchArrayMap).map(([visitorKey, searchFnsArray]) => { return [ visitorKey, (0, utils_1.createMultipleSearchFunctionsExecutor)(searchFnsArray), ]; })); preparingVisitorsTime += performance.now() - preparingVisitorsStart; preparationTime += performance.now() - prepStart; if (!telemetryReported[type]) { telemetry.reportConfig({ ruleType: type === 'problem' ? 'error' : 'warning', queriesCount: queryCodes.length, mode_include: searchModes.includes('include') || settings.some(({ mode }) => mode === undefined), mode_exact: searchModes.includes('exact'), mode_include_w_order: searchModes.includes('include-with-order'), fileFilters_include: settings.some(({ includeFiles }) => includeFiles !== undefined), fileFilters_exclude: settings.some(({ excludeFiles }) => excludeFiles !== undefined), }); telemetryReported[type] = true; } return visitors; }, }); exports.createLintCode = createLintCode;