fix(providence-analytics): provide target/reference result match

This commit is contained in:
Thijs Louisse 2020-08-05 11:25:12 +02:00
parent cc251ed46c
commit d7f0807903
4 changed files with 111 additions and 56 deletions

View file

@ -9,26 +9,6 @@ const { InputDataService } = require('../../services/InputDataService.js');
const { aForEach } = require('../../utils/async-array-utils.js'); const { aForEach } = require('../../utils/async-array-utils.js');
const { getFilePathRelativeFromRoot } = require('../../utils/get-file-path-relative-from-root.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 * @desc analyzes one entry: the callback can traverse a given ast for each entry
* @param {AstDataProject[]} astDataProjects * @param {AstDataProject[]} astDataProjects
@ -86,6 +66,14 @@ function ensureAnalyzerResultFormat(queryOutput, configuration, analyzer) {
delete aResult.analyzerMeta.configuration.referenceProjectPath; delete aResult.analyzerMeta.configuration.referenceProjectPath;
delete aResult.analyzerMeta.configuration.targetProjectPath; 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)) { if (Array.isArray(aResult.queryOutput)) {
aResult.queryOutput.forEach(projectOutput => { aResult.queryOutput.forEach(projectOutput => {
if (projectOutput.project) { if (projectOutput.project) {
@ -123,6 +111,16 @@ function checkForMatchCompatibility(referencePath, targetPath) {
return { compatible: true }; 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 { class Analyzer {
constructor() { constructor() {
this.requiredAst = 'babel'; this.requiredAst = 'babel';
@ -132,15 +130,39 @@ class Analyzer {
return false; 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 * @param {AnalyzerConfig} cfg
* @returns {CachedAnalyzerResult|undefined} * @returns {CachedAnalyzerResult|undefined}
*/ */
_prepare(cfg) { _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); 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, analyzerConfig: cfg,
}); });
// If we have a provided result cfg.referenceProjectResult, we assume the providing
// party provides compatible results for now...
if (cfg.referenceProjectPath) { if (cfg.referenceProjectPath) {
this.referenceProjectMeta = InputDataService.getProjectMeta(cfg.referenceProjectPath, true);
const { compatible, reason } = checkForMatchCompatibility( const { compatible, reason } = checkForMatchCompatibility(
cfg.referenceProjectPath, cfg.referenceProjectPath,
cfg.targetProjectPath, cfg.targetProjectPath,
@ -176,7 +198,7 @@ class Analyzer {
/** /**
* See if we maybe already have our result in cache in the file-system. * See if we maybe already have our result in cache in the file-system.
*/ */
const cachedResult = getCachedAnalyzerResult({ const cachedResult = Analyzer._getCachedAnalyzerResult({
analyzerName: this.name, analyzerName: this.name,
identifier: this.identifier, identifier: this.identifier,
}); });
@ -186,13 +208,16 @@ class Analyzer {
} }
LogService.info(`starting ${LogService.pad(this.name, 16)} for ${this.identifier}`); LogService.info(`starting ${LogService.pad(this.name, 16)} for ${this.identifier}`);
/** /**
* Get reference and search-target data * Get reference and search-target data
*/ */
this.targetData = InputDataService.createDataObject( if (!cfg.targetProjectResult) {
[cfg.targetProjectPath], this.targetData = InputDataService.createDataObject(
cfg.gatherFilesConfig, [cfg.targetProjectPath],
); cfg.gatherFilesConfig,
);
}
if (cfg.referenceProjectPath) { if (cfg.referenceProjectPath) {
this.referenceData = InputDataService.createDataObject( this.referenceData = InputDataService.createDataObject(
@ -251,6 +276,27 @@ class Analyzer {
*/ */
return this._finalize(queryOutput, cfg); 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 }; module.exports = { Analyzer };

View file

@ -83,6 +83,10 @@ function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerResult, c
importEntryResult.importSpecifiers.includes(exportSpecifier) || importEntryResult.importSpecifiers.includes(exportSpecifier) ||
importEntryResult.importSpecifiers.includes('[*]'); importEntryResult.importSpecifiers.includes('[*]');
if (!hasExportSpecifierImported) {
return;
}
/** /**
* @example * @example
* exportFile './foo.js' * exportFile './foo.js'
@ -98,14 +102,13 @@ function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerResult, c
externalRootPath: cfg.referenceProjectPath, externalRootPath: cfg.referenceProjectPath,
}); });
// TODO: transitive deps recognition. Could also be distinct post processor if (!isFromSameSource) {
// // export { z } from '../foo.js' return;
// // import { z } from '@reference/foo.js'
// (exportEntryResult.normalizedSource === importEntryResult.normalizedSource)
if (hasExportSpecifierImported && isFromSameSource) {
filteredImportsList.add(`${importProject}::${file}`);
} }
// TODO: transitive deps recognition? Could also be distinct post processor
filteredImportsList.add(`${importProject}::${file}`);
}), }),
); );
storeResult(resultsObj, exportId, filteredImportsList, exportEntry.meta); storeResult(resultsObj, exportId, filteredImportsList, exportEntry.meta);
@ -201,15 +204,15 @@ class MatchImportsAnalyzer extends Analyzer {
* @property {GatherFilesConfig} [gatherFilesConfig] * @property {GatherFilesConfig} [gatherFilesConfig]
* @property {array} [referenceProjectPath] reference paths * @property {array} [referenceProjectPath] reference paths
* @property {array} [targetProjectPath] search target paths * @property {array} [targetProjectPath] search target paths
* @property {FindExportsAnalyzerResult} [exportsAnalyzerResult] * @property {FindImportsAnalyzerResult} [targetProjectResult]
* @property {FindImportsAnalyzerResult} [importsAnalyzerResult] * @property {FindExportsAnalyzerResult} [referenceProjectResult]
*/ */
const cfg = { const cfg = {
gatherFilesConfig: {}, gatherFilesConfig: {},
referenceProjectPath: null, referenceProjectPath: null,
targetProjectPath: null, targetProjectPath: null,
exportsAnalyzerResult: null, targetProjectResult: null,
importsAnalyzerResult: null, referenceProjectResult: null,
...customConfig, ...customConfig,
}; };
@ -224,25 +227,25 @@ class MatchImportsAnalyzer extends Analyzer {
/** /**
* Traverse * Traverse
*/ */
let { exportsAnalyzerResult } = cfg; let { referenceProjectResult } = cfg;
if (!exportsAnalyzerResult) { if (!referenceProjectResult) {
const findExportsAnalyzer = new FindExportsAnalyzer(); const findExportsAnalyzer = new FindExportsAnalyzer();
exportsAnalyzerResult = await findExportsAnalyzer.execute({ referenceProjectResult = await findExportsAnalyzer.execute({
metaConfig: cfg.metaConfig, metaConfig: cfg.metaConfig,
targetProjectPath: cfg.referenceProjectPath, targetProjectPath: cfg.referenceProjectPath,
}); });
} }
let { importsAnalyzerResult } = cfg; let { targetProjectResult } = cfg;
if (!importsAnalyzerResult) { if (!targetProjectResult) {
const findImportsAnalyzer = new FindImportsAnalyzer(); const findImportsAnalyzer = new FindImportsAnalyzer();
importsAnalyzerResult = await findImportsAnalyzer.execute({ targetProjectResult = await findImportsAnalyzer.execute({
metaConfig: cfg.metaConfig, metaConfig: cfg.metaConfig,
targetProjectPath: cfg.targetProjectPath, targetProjectPath: cfg.targetProjectPath,
}); });
} }
const queryOutput = matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerResult, cfg); const queryOutput = matchImportsPostprocess(referenceProjectResult, targetProjectResult, cfg);
/** /**
* Finalize * Finalize

View file

@ -28,9 +28,6 @@ const {
appendProjectDependencyPaths, appendProjectDependencyPaths,
} = cliHelpersModule; } = cliHelpersModule;
// Prevent (node:2860) MaxListenersExceededWarning
commander.setMaxListeners(100);
const queryResults = []; const queryResults = [];
const rootDir = pathLib.resolve(__dirname, '../../'); const rootDir = pathLib.resolve(__dirname, '../../');
@ -63,6 +60,9 @@ describe('Providence CLI', () => {
let qConfStub; let qConfStub;
before(() => { before(() => {
// Prevent MaxListenersExceededWarning
commander.setMaxListeners(100);
mockWriteToJson(queryResults); mockWriteToJson(queryResults);
suppressNonCriticalLogs(); suppressNonCriticalLogs();
@ -105,6 +105,8 @@ describe('Providence CLI', () => {
}); });
after(() => { after(() => {
commander.setMaxListeners(10);
restoreSuppressNonCriticalLogs(); restoreSuppressNonCriticalLogs();
restoreMockedProjects(); restoreMockedProjects();
restoreWriteToJson(); restoreWriteToJson();

View file

@ -312,17 +312,18 @@ describe('Analyzer "match-imports"', () => {
describe('Configuration', () => { describe('Configuration', () => {
it(`allows to provide results of FindExportsAnalyzer and FindImportsAnalyzer`, async () => { it(`allows to provide results of FindExportsAnalyzer and FindImportsAnalyzer`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
const importsAnalyzerResult = await new FindImportsAnalyzer().execute({ const findImportsResult = await new FindImportsAnalyzer().execute({
targetProjectPath: searchTargetProject.path, targetProjectPath: searchTargetProject.path,
}); });
const exportsAnalyzerResult = await new FindExportsAnalyzer().execute({ const findExportsResult = await new FindExportsAnalyzer().execute({
targetProjectPath: referenceProject.path, targetProjectPath: referenceProject.path,
}); });
await providence(matchImportsQueryConfig, {
..._providenceCfg, const matchImportsQueryConfigExt = QueryService.getQueryConfigFromAnalyzer('match-imports', {
importsAnalyzerResult, targetProjectResult: findImportsResult,
exportsAnalyzerResult, referenceProjectResult: findExportsResult,
}); });
await providence(matchImportsQueryConfigExt, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expectedExportIdsDirect.forEach(targetId => { expectedExportIdsDirect.forEach(targetId => {
@ -333,5 +334,8 @@ describe('Analyzer "match-imports"', () => {
testMatchedEntry(targetId, queryResult, ['./target-src/indirect-imports.js']); 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 () => {});
}); });
}); });