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 { 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
*/
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 };

View file

@ -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

View file

@ -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();

View file

@ -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 () => {});
});
});