diff --git a/packages/providence-analytics/src/program/analyzers/helpers/Analyzer.js b/packages/providence-analytics/src/program/analyzers/helpers/Analyzer.js index 50643d11d..e71811189 100644 --- a/packages/providence-analytics/src/program/analyzers/helpers/Analyzer.js +++ b/packages/providence-analytics/src/program/analyzers/helpers/Analyzer.js @@ -9,26 +9,6 @@ const { InputDataService } = require('../../services/InputDataService.js'); const { aForEach } = require('../../utils/async-array-utils.js'); const { getFilePathRelativeFromRoot } = require('../../utils/get-file-path-relative-from-root.js'); -/** - * @desc Gets a cached result from ReportService. Since ReportService slightly modifies analyzer - * output, we 'unwind' before we return... - * @param {object} config - * @param {string} config.analyzerName - * @param {string} config.identifier - */ -function getCachedAnalyzerResult({ analyzerName, identifier }) { - const cachedResult = ReportService.getCachedResult({ analyzerName, identifier }); - if (!cachedResult) { - return; - } - LogService.success(`cached version found for ${identifier}`); - - const { queryOutput } = cachedResult; - const { analyzerMeta } = cachedResult.meta; - analyzerMeta.__fromCache = true; - return { analyzerMeta, queryOutput }; // eslint-disable-line consistent-return -} - /** * @desc analyzes one entry: the callback can traverse a given ast for each entry * @param {AstDataProject[]} astDataProjects @@ -86,6 +66,14 @@ function ensureAnalyzerResultFormat(queryOutput, configuration, analyzer) { delete aResult.analyzerMeta.configuration.referenceProjectPath; delete aResult.analyzerMeta.configuration.targetProjectPath; + const { referenceProjectResult, targetProjectResult } = aResult.analyzerMeta.configuration; + + if (referenceProjectResult) { + delete aResult.analyzerMeta.configuration.referenceProjectResult; + } else if (targetProjectResult) { + delete aResult.analyzerMeta.configuration.targetProjectResult; + } + if (Array.isArray(aResult.queryOutput)) { aResult.queryOutput.forEach(projectOutput => { if (projectOutput.project) { @@ -123,6 +111,16 @@ function checkForMatchCompatibility(referencePath, targetPath) { return { compatible: true }; } +/** + * If in json format, 'unwind' to be compatible for analysis... + * @param {AnalyzerResult} targetOrReferenceProjectResult + */ +function unwindJsonResult(targetOrReferenceProjectResult) { + const { queryOutput } = targetOrReferenceProjectResult; + const { analyzerMeta } = targetOrReferenceProjectResult.meta; + return { queryOutput, analyzerMeta }; +} + class Analyzer { constructor() { this.requiredAst = 'babel'; @@ -132,15 +130,39 @@ class Analyzer { return false; } + /** + * In a MatchAnalyzer, two Analyzers (a reference and targer) are run. + * For instance, in a MatchImportsAnalyzer, a FindExportsAnalyzer and FinImportsAnalyzer are run. + * Their results can be provided as config params. + * If they are stored in json format, 'unwind' them to be compatible for analysis... + * @param {MatchAnalyzerConfig} cfg + */ + static __unwindProvidedResults(cfg) { + if (cfg.targetProjectResult && !cfg.targetProjectResult.analyzerMeta) { + cfg.targetProjectResult = unwindJsonResult(cfg.targetProjectResult); + } + if (cfg.referenceProjectResult && !cfg.referenceProjectResult.analyzerMeta) { + cfg.referenceProjectResult = unwindJsonResult(cfg.referenceProjectResult); + } + } + /** * @param {AnalyzerConfig} cfg * @returns {CachedAnalyzerResult|undefined} */ _prepare(cfg) { - this.targetProjectMeta = InputDataService.getProjectMeta(cfg.targetProjectPath, true); + this.constructor.__unwindProvidedResults(cfg); - if (cfg.referenceProjectPath) { + if (!cfg.targetProjectResult) { + this.targetProjectMeta = InputDataService.getProjectMeta(cfg.targetProjectPath, true); + } else { + this.targetProjectMeta = cfg.targetProjectResult.analyzerMeta.targetProject; + } + + if (cfg.referenceProjectPath && !cfg.referenceProjectResult) { this.referenceProjectMeta = InputDataService.getProjectMeta(cfg.referenceProjectPath, true); + } else if (cfg.referenceProjectResult) { + this.referenceProjectMeta = cfg.referenceProjectResult.analyzerMeta.targetProject; } /** @@ -152,9 +174,9 @@ class Analyzer { analyzerConfig: cfg, }); + // If we have a provided result cfg.referenceProjectResult, we assume the providing + // party provides compatible results for now... if (cfg.referenceProjectPath) { - this.referenceProjectMeta = InputDataService.getProjectMeta(cfg.referenceProjectPath, true); - const { compatible, reason } = checkForMatchCompatibility( cfg.referenceProjectPath, cfg.targetProjectPath, @@ -176,7 +198,7 @@ class Analyzer { /** * See if we maybe already have our result in cache in the file-system. */ - const cachedResult = getCachedAnalyzerResult({ + const cachedResult = Analyzer._getCachedAnalyzerResult({ analyzerName: this.name, identifier: this.identifier, }); @@ -186,13 +208,16 @@ class Analyzer { } LogService.info(`starting ${LogService.pad(this.name, 16)} for ${this.identifier}`); + /** * Get reference and search-target data */ - this.targetData = InputDataService.createDataObject( - [cfg.targetProjectPath], - cfg.gatherFilesConfig, - ); + if (!cfg.targetProjectResult) { + this.targetData = InputDataService.createDataObject( + [cfg.targetProjectPath], + cfg.gatherFilesConfig, + ); + } if (cfg.referenceProjectPath) { this.referenceData = InputDataService.createDataObject( @@ -251,6 +276,27 @@ class Analyzer { */ return this._finalize(queryOutput, cfg); } + + /** + * @desc Gets a cached result from ReportService. Since ReportService slightly modifies analyzer + * output, we 'unwind' before we return... + * @param {object} config + * @param {string} config.analyzerName + * @param {string} config.identifier + * @returns {AnalyzerResult|undefined} + */ + static _getCachedAnalyzerResult({ analyzerName, identifier }) { + const cachedResult = ReportService.getCachedResult({ analyzerName, identifier }); + if (!cachedResult) { + return undefined; + } + LogService.success(`cached version found for ${identifier}`); + + /** @type {AnalyzerResult} */ + const result = unwindJsonResult(cachedResult); + result.analyzerMeta.__fromCache = true; + return result; + } } module.exports = { Analyzer }; diff --git a/packages/providence-analytics/src/program/analyzers/match-imports.js b/packages/providence-analytics/src/program/analyzers/match-imports.js index e258c6f8c..e682def5a 100644 --- a/packages/providence-analytics/src/program/analyzers/match-imports.js +++ b/packages/providence-analytics/src/program/analyzers/match-imports.js @@ -83,6 +83,10 @@ function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerResult, c importEntryResult.importSpecifiers.includes(exportSpecifier) || importEntryResult.importSpecifiers.includes('[*]'); + if (!hasExportSpecifierImported) { + return; + } + /** * @example * exportFile './foo.js' @@ -98,14 +102,13 @@ function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerResult, c externalRootPath: cfg.referenceProjectPath, }); - // TODO: transitive deps recognition. Could also be distinct post processor - // // export { z } from '../foo.js' - // // import { z } from '@reference/foo.js' - // (exportEntryResult.normalizedSource === importEntryResult.normalizedSource) - - if (hasExportSpecifierImported && isFromSameSource) { - filteredImportsList.add(`${importProject}::${file}`); + if (!isFromSameSource) { + return; } + + // TODO: transitive deps recognition? Could also be distinct post processor + + filteredImportsList.add(`${importProject}::${file}`); }), ); storeResult(resultsObj, exportId, filteredImportsList, exportEntry.meta); @@ -201,15 +204,15 @@ class MatchImportsAnalyzer extends Analyzer { * @property {GatherFilesConfig} [gatherFilesConfig] * @property {array} [referenceProjectPath] reference paths * @property {array} [targetProjectPath] search target paths - * @property {FindExportsAnalyzerResult} [exportsAnalyzerResult] - * @property {FindImportsAnalyzerResult} [importsAnalyzerResult] + * @property {FindImportsAnalyzerResult} [targetProjectResult] + * @property {FindExportsAnalyzerResult} [referenceProjectResult] */ const cfg = { gatherFilesConfig: {}, referenceProjectPath: null, targetProjectPath: null, - exportsAnalyzerResult: null, - importsAnalyzerResult: null, + targetProjectResult: null, + referenceProjectResult: null, ...customConfig, }; @@ -224,25 +227,25 @@ class MatchImportsAnalyzer extends Analyzer { /** * Traverse */ - let { exportsAnalyzerResult } = cfg; - if (!exportsAnalyzerResult) { + let { referenceProjectResult } = cfg; + if (!referenceProjectResult) { const findExportsAnalyzer = new FindExportsAnalyzer(); - exportsAnalyzerResult = await findExportsAnalyzer.execute({ + referenceProjectResult = await findExportsAnalyzer.execute({ metaConfig: cfg.metaConfig, targetProjectPath: cfg.referenceProjectPath, }); } - let { importsAnalyzerResult } = cfg; - if (!importsAnalyzerResult) { + let { targetProjectResult } = cfg; + if (!targetProjectResult) { const findImportsAnalyzer = new FindImportsAnalyzer(); - importsAnalyzerResult = await findImportsAnalyzer.execute({ + targetProjectResult = await findImportsAnalyzer.execute({ metaConfig: cfg.metaConfig, targetProjectPath: cfg.targetProjectPath, }); } - const queryOutput = matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerResult, cfg); + const queryOutput = matchImportsPostprocess(referenceProjectResult, targetProjectResult, cfg); /** * Finalize diff --git a/packages/providence-analytics/test-node/cli/cli.test.js b/packages/providence-analytics/test-node/cli/cli.test.js index 2454ed9dc..c94e987df 100644 --- a/packages/providence-analytics/test-node/cli/cli.test.js +++ b/packages/providence-analytics/test-node/cli/cli.test.js @@ -28,9 +28,6 @@ const { appendProjectDependencyPaths, } = cliHelpersModule; -// Prevent (node:2860) MaxListenersExceededWarning -commander.setMaxListeners(100); - const queryResults = []; const rootDir = pathLib.resolve(__dirname, '../../'); @@ -63,6 +60,9 @@ describe('Providence CLI', () => { let qConfStub; before(() => { + // Prevent MaxListenersExceededWarning + commander.setMaxListeners(100); + mockWriteToJson(queryResults); suppressNonCriticalLogs(); @@ -105,6 +105,8 @@ describe('Providence CLI', () => { }); after(() => { + commander.setMaxListeners(10); + restoreSuppressNonCriticalLogs(); restoreMockedProjects(); restoreWriteToJson(); diff --git a/packages/providence-analytics/test-node/program/analyzers/match-imports.test.js b/packages/providence-analytics/test-node/program/analyzers/match-imports.test.js index fd131203c..1b342c973 100644 --- a/packages/providence-analytics/test-node/program/analyzers/match-imports.test.js +++ b/packages/providence-analytics/test-node/program/analyzers/match-imports.test.js @@ -312,17 +312,18 @@ describe('Analyzer "match-imports"', () => { describe('Configuration', () => { it(`allows to provide results of FindExportsAnalyzer and FindImportsAnalyzer`, async () => { mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const importsAnalyzerResult = await new FindImportsAnalyzer().execute({ + const findImportsResult = await new FindImportsAnalyzer().execute({ targetProjectPath: searchTargetProject.path, }); - const exportsAnalyzerResult = await new FindExportsAnalyzer().execute({ + const findExportsResult = await new FindExportsAnalyzer().execute({ targetProjectPath: referenceProject.path, }); - await providence(matchImportsQueryConfig, { - ..._providenceCfg, - importsAnalyzerResult, - exportsAnalyzerResult, + + const matchImportsQueryConfigExt = QueryService.getQueryConfigFromAnalyzer('match-imports', { + targetProjectResult: findImportsResult, + referenceProjectResult: findExportsResult, }); + await providence(matchImportsQueryConfigExt, _providenceCfg); const queryResult = queryResults[0]; expectedExportIdsDirect.forEach(targetId => { @@ -333,5 +334,8 @@ describe('Analyzer "match-imports"', () => { testMatchedEntry(targetId, queryResult, ['./target-src/indirect-imports.js']); }); }); + + // TODO: Test this unwind functionality in a generic MatchAnalyzer test + it.skip(`allows to provide results of FindExportsAnalyzer and FindImportsAnalyzer from external jsons`, async () => {}); }); });