Merge pull request #845 from ing-bank/chore/providenceCliTests

feat(providence-analytics): allow target dependencies via cli
This commit is contained in:
Joren Broekema 2020-08-10 12:07:16 +02:00 committed by GitHub
commit 8a7b550f68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 582 additions and 191 deletions

View file

@ -0,0 +1,5 @@
---
'providence-analytics': patch
---
feat: allow target dependencies via cli

View file

@ -60,7 +60,7 @@ function pathsArrayFromCs(t, cwd = process.cwd()) {
* @param {object} eCfg external configuration. Usually providence.conf.js * @param {object} eCfg external configuration. Usually providence.conf.js
* @returns {string[]} * @returns {string[]}
*/ */
function pathsArrayFromCollectionName(name, colType = 'search-target', eCfg) { function pathsArrayFromCollectionName(name, colType = 'search-target', eCfg, cwd) {
let collection; let collection;
if (colType === 'search-target') { if (colType === 'search-target') {
collection = eCfg.searchTargetCollections; collection = eCfg.searchTargetCollections;
@ -68,7 +68,7 @@ function pathsArrayFromCollectionName(name, colType = 'search-target', eCfg) {
collection = eCfg.referenceCollections; collection = eCfg.referenceCollections;
} }
if (collection && collection[name]) { if (collection && collection[name]) {
return pathsArrayFromCs(collection[name].join(',')); return pathsArrayFromCs(collection[name].join(','), cwd);
} }
return undefined; return undefined;
} }
@ -107,10 +107,20 @@ function targetDefault() {
/** /**
* @desc Returns all sub projects matching condition supplied in matchFn * @desc Returns all sub projects matching condition supplied in matchFn
* @param {string[]} searchTargetPaths all search-target project paths * @param {string[]} searchTargetPaths all search-target project paths
* @param {function} matchFn filters out packages we're interested in * @param {string} matchPattern base for RegExp
* @param {string[]} modes * @param {string[]} modes
*/ */
async function appendProjectDependencyPaths(rootPaths, matchFn, modes = ['npm', 'bower']) { async function appendProjectDependencyPaths(rootPaths, matchPattern, modes = ['npm', 'bower']) {
let matchFn;
if (matchPattern) {
if (matchPattern.startsWith('/') && matchPattern.endsWith('/')) {
matchFn = (_, d) => new RegExp(matchPattern.slice(1, -1)).test(d);
} else {
LogService.error(
`[appendProjectDependencyPaths] Please provide a matchPattern enclosed by '/'. Found: ${matchPattern}`,
);
}
}
const depProjectPaths = []; const depProjectPaths = [];
await aForEach(rootPaths, async targetPath => { await aForEach(rootPaths, async targetPath => {
await aForEach(modes, async mode => { await aForEach(modes, async mode => {

View file

@ -8,22 +8,18 @@ const providenceModule = require('../program/providence.js');
const { LogService } = require('../program/services/LogService.js'); const { LogService } = require('../program/services/LogService.js');
const { QueryService } = require('../program/services/QueryService.js'); const { QueryService } = require('../program/services/QueryService.js');
const { InputDataService } = require('../program/services/InputDataService.js'); const { InputDataService } = require('../program/services/InputDataService.js');
const { promptAnalyzerMenu, promptAnalyzerConfigMenu } = require('./prompt-analyzer-menu.js'); const promptModule = require('./prompt-analyzer-menu.js');
const { const cliHelpers = require('./cli-helpers.js');
extensionsFromCs,
setQueryMethod,
targetDefault,
appendProjectDependencyPaths,
installDeps,
pathsArrayFromCollectionName,
pathsArrayFromCs,
} = require('./cli-helpers.js');
const extendDocsModule = require('./generate-extend-docs-data.js'); const extendDocsModule = require('./generate-extend-docs-data.js');
const { extensionsFromCs, setQueryMethod, targetDefault, installDeps } = cliHelpers;
const { version } = require('../../package.json'); const { version } = require('../../package.json');
async function cli({ cwd, addProjectDependencyPaths } = {}) { async function cli({ cwd } = {}) {
let resolveCli; let resolveCli;
let rejectCli; let rejectCli;
const cliPromise = new Promise((resolve, reject) => { const cliPromise = new Promise((resolve, reject) => {
resolveCli = resolve; resolveCli = resolve;
rejectCli = reject; rejectCli = reject;
@ -39,7 +35,6 @@ async function cli({ cwd, addProjectDependencyPaths } = {}) {
let regexSearchOptions; let regexSearchOptions;
const externalConfig = InputDataService.getExternalConfig(); const externalConfig = InputDataService.getExternalConfig();
console.log('externalConfig', externalConfig);
async function getQueryInputData( async function getQueryInputData(
/* eslint-disable no-shadow */ /* eslint-disable no-shadow */
@ -48,7 +43,6 @@ async function cli({ cwd, addProjectDependencyPaths } = {}) {
featureOptions, featureOptions,
analyzerOptions, analyzerOptions,
/* eslint-enable no-shadow */ /* eslint-enable no-shadow */
showAnalyzerConfigMenu,
) { ) {
let queryConfig = null; let queryConfig = null;
let queryMethod = null; let queryMethod = null;
@ -64,11 +58,14 @@ async function cli({ cwd, addProjectDependencyPaths } = {}) {
} else if (searchMode === 'analyzer-query') { } else if (searchMode === 'analyzer-query') {
let { name, config } = analyzerOptions; let { name, config } = analyzerOptions;
if (!name) { if (!name) {
const answers = await promptAnalyzerMenu(); const answers = await promptModule.promptAnalyzerMenu();
name = answers.analyzerName; name = answers.analyzerName;
} }
if (showAnalyzerConfigMenu && !config) { if (!config) {
const answers = await promptAnalyzerConfigMenu(name, analyzerOptions.promptOptionalConfig); const answers = await promptModule.promptAnalyzerConfigMenu(
name,
analyzerOptions.promptOptionalConfig,
);
config = answers.analyzerConfig; config = answers.analyzerConfig;
} }
// Will get metaConfig from ./providence.conf.js // Will get metaConfig from ./providence.conf.js
@ -97,11 +94,18 @@ async function cli({ cwd, addProjectDependencyPaths } = {}) {
referencePaths = commander.referenceCollection || commander.referencePaths; referencePaths = commander.referenceCollection || commander.referencePaths;
} }
let extendedSearchTargets; /**
if (addProjectDependencyPaths) { * May or may not include dependencies of search target
extendedSearchTargets = await appendProjectDependencyPaths(searchTargetPaths); * @type {string[]}
*/
let totalSearchTargets;
if (commander.targetDependencies !== undefined) {
totalSearchTargets = await cliHelpers.appendProjectDependencyPaths(
searchTargetPaths,
commander.targetDependencies,
);
} else { } else {
extendedSearchTargets = searchTargetPaths; totalSearchTargets = searchTargetPaths;
} }
// TODO: filter out: // TODO: filter out:
@ -120,7 +124,7 @@ async function cli({ cwd, addProjectDependencyPaths } = {}) {
}, },
debugEnabled: commander.debug, debugEnabled: commander.debug,
queryMethod, queryMethod,
targetProjectPaths: extendedSearchTargets, targetProjectPaths: totalSearchTargets,
referenceProjectPaths: referencePaths, referenceProjectPaths: referencePaths,
targetProjectRootPaths: searchTargetPaths, targetProjectRootPaths: searchTargetPaths,
writeLogFile: commander.writeLogFile, writeLogFile: commander.writeLogFile,
@ -157,7 +161,7 @@ async function cli({ cwd, addProjectDependencyPaths } = {}) {
'-t, --search-target-paths [targets]', '-t, --search-target-paths [targets]',
`path(s) to project(s) on which analysis/querying should take place. Requires `path(s) to project(s) on which analysis/querying should take place. Requires
a list of comma seperated values relative to project root`, a list of comma seperated values relative to project root`,
v => pathsArrayFromCs(v, cwd), v => cliHelpers.pathsArrayFromCs(v, cwd),
targetDefault(), targetDefault(),
) )
.option( .option(
@ -165,31 +169,39 @@ async function cli({ cwd, addProjectDependencyPaths } = {}) {
`path(s) to project(s) which serve as a reference (applicable for certain analyzers like `path(s) to project(s) which serve as a reference (applicable for certain analyzers like
'match-imports'). Requires a list of comma seperated values relative to 'match-imports'). Requires a list of comma seperated values relative to
project root (like 'node_modules/lion-based-ui, node_modules/lion-based-ui-labs').`, project root (like 'node_modules/lion-based-ui, node_modules/lion-based-ui-labs').`,
v => pathsArrayFromCs(v, cwd), v => cliHelpers.pathsArrayFromCs(v, cwd),
InputDataService.referenceProjectPaths, InputDataService.referenceProjectPaths,
) )
.option('-w, --whitelist [whitelist]', `whitelisted paths, like './src, ./packages/*'`, v => .option('-w, --whitelist [whitelist]', `whitelisted paths, like './src, ./packages/*'`, v =>
pathsArrayFromCs(v, cwd), cliHelpers.pathsArrayFromCs(v, cwd),
) )
.option( .option(
'--whitelist-reference [whitelist-reference]', '--whitelist-reference [whitelist-reference]',
`whitelisted paths for reference, like './src, ./packages/*'`, `whitelisted paths for reference, like './src, ./packages/*'`,
v => pathsArrayFromCs(v, cwd), v => cliHelpers.pathsArrayFromCs(v, cwd),
) )
.option( .option(
'--search-target-collection [collection-name]', '--search-target-collection [collection-name]',
`path(s) to project(s) which serve as a reference (applicable for certain analyzers like `path(s) to project(s) which serve as a reference (applicable for certain analyzers like
'match-imports'). Should be a collection defined in providence.conf.js as paths relative to 'match-imports'). Should be a collection defined in providence.conf.js as paths relative to
project root.`, project root.`,
v => pathsArrayFromCollectionName(v, 'search-target', externalConfig), v => cliHelpers.pathsArrayFromCollectionName(v, 'search-target', externalConfig),
) )
.option( .option(
'--reference-collection [collection-name]', '--reference-collection [collection-name]',
`path(s) to project(s) on which analysis/querying should take place. Should be a collection `path(s) to project(s) on which analysis/querying should take place. Should be a collection
defined in providence.conf.js as paths relative to project root.`, defined in providence.conf.js as paths relative to project root.`,
v => pathsArrayFromCollectionName(v, 'reference', externalConfig), v => cliHelpers.pathsArrayFromCollectionName(v, 'reference', externalConfig),
) )
.option('--write-log-file', `Writes all logs to 'providence.log' file`); .option('--write-log-file', `Writes all logs to 'providence.log' file`)
.option(
'--target-dependencies [target-dependencies]',
`For all search targets, will include all its dependencies
(node_modules and bower_components). When --target-dependencies is applied
without argument, it will act as boolean and include all dependencies.
When a regex is supplied like --target-dependencies /^my-brand-/, it will filter
all packages that comply with the regex`,
);
commander commander
.command('search <regex>') .command('search <regex>')

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
@ -48,7 +28,7 @@ async function analyzePerAstEntry(projectData, astAnalysis) {
} }
/** /**
* @desc This method ensures that the result returned by an analyzer always has a consitent format, * @desc This method ensures that the result returned by an analyzer always has a consistent format.
* By returning the configuration for the queryOutput, it will be possible to run later queries * By returning the configuration for the queryOutput, it will be possible to run later queries
* under the same circumstances * under the same circumstances
* @param {array} queryOutput * @param {array} queryOutput
@ -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

@ -49,17 +49,21 @@ function fromImportToExportPerspective({
// we still need to check if we're not dealing with a folder. // we still need to check if we're not dealing with a folder.
// - '@open-wc/x/y.js' -> '@open-wc/x/y.js' or... '@open-wc/x/y.js/index.js' ? // - '@open-wc/x/y.js' -> '@open-wc/x/y.js' or... '@open-wc/x/y.js/index.js' ?
// - or 'lion-based-ui/test' -> 'lion-based-ui/test/index.js' or 'lion-based-ui/test' ? // - or 'lion-based-ui/test' -> 'lion-based-ui/test/index.js' or 'lion-based-ui/test' ?
const pathToCheck = pathLib.resolve(externalRootPath, `./${localPath}`); if (externalRootPath) {
const pathToCheck = pathLib.resolve(externalRootPath, `./${localPath}`);
if (fs.existsSync(pathToCheck)) { if (fs.existsSync(pathToCheck)) {
const stat = fs.statSync(pathToCheck); const stat = fs.statSync(pathToCheck);
if (stat && stat.isFile()) { if (stat && stat.isFile()) {
return `./${localPath}`; // '/path/to/lion-based-ui/fol.der' is a file return `./${localPath}`; // '/path/to/lion-based-ui/fol.der' is a file
}
return `./${localPath}/index.js`; // '/path/to/lion-based-ui/fol.der' is a folder
// eslint-disable-next-line no-else-return
} else if (fs.existsSync(`${pathToCheck}.js`)) {
return `./${localPath}.js`; // '/path/to/lion-based-ui/fol.der' is file '/path/to/lion-based-ui/fol.der.js'
} }
return `./${localPath}/index.js`; // '/path/to/lion-based-ui/fol.der' is a folder } else {
// eslint-disable-next-line no-else-return return `./${localPath}`;
} else if (fs.existsSync(`${pathToCheck}.js`)) {
return `./${localPath}.js`; // '/path/to/lion-based-ui/fol.der' is file '/path/to/lion-based-ui/fol.der.js'
} }
} else { } else {
// like '@lion/core' // like '@lion/core'

View file

@ -19,6 +19,20 @@ function storeResult(resultsObj, exportId, filteredList, meta) {
resultsObj[exportId].files = [...(resultsObj[exportId].files || []), ...Array.from(filteredList)]; resultsObj[exportId].files = [...(resultsObj[exportId].files || []), ...Array.from(filteredList)];
} }
/**
* Needed in case fromImportToExportPerspective does not have a
* externalRootPath supplied.
* @param {string} exportPath exportEntry.file
* @param {string} translatedImportPath result of fromImportToExportPerspective
*/
function compareImportAndExportPaths(exportPath, translatedImportPath) {
return (
exportPath === translatedImportPath ||
exportPath === `${translatedImportPath}.js` ||
exportPath === `${translatedImportPath}/index.js`
);
}
/** /**
* @param {FindExportsAnalyzerResult} exportsAnalyzerResult * @param {FindExportsAnalyzerResult} exportsAnalyzerResult
* @param {FindImportsAnalyzerResult} importsAnalyzerResult * @param {FindImportsAnalyzerResult} importsAnalyzerResult
@ -83,6 +97,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'
@ -90,22 +108,23 @@ function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerResult, c
* importFile 'importing-target-project/file.js' * importFile 'importing-target-project/file.js'
* => import { z } from '@reference/foo.js' * => import { z } from '@reference/foo.js'
*/ */
const isFromSameSource = const fromImportToExport = fromImportToExportPerspective({
exportEntry.file === requestedExternalSource: importEntryResult.normalizedSource,
fromImportToExportPerspective({ externalProjectMeta: exportsProjectObj,
requestedExternalSource: importEntryResult.normalizedSource, externalRootPath: cfg.referenceProjectResult ? null : cfg.referenceProjectPath,
externalProjectMeta: exportsProjectObj, });
externalRootPath: cfg.referenceProjectPath, const isFromSameSource = compareImportAndExportPaths(
}); exportEntry.file,
fromImportToExport,
);
// 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 +220,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 +243,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

@ -329,7 +329,7 @@ function matchPathsPostprocess(
} }
/** /**
* Designed to work in conjunction with npm package `extend-docs`. * Designed to work in conjunction with npm package `babel-plugin-extend-docs`.
* It will lookup all class exports from reference project A (and store their available paths) and * It will lookup all class exports from reference project A (and store their available paths) and
* matches them against all imports of project B that extend exported class (and store their * matches them against all imports of project B that extend exported class (and store their
* available paths). * available paths).
@ -426,7 +426,8 @@ class MatchPathsAnalyzer extends Analyzer {
const targetMatchSubclassesResult = await targetMatchSubclassesAnalyzer.execute({ const targetMatchSubclassesResult = await targetMatchSubclassesAnalyzer.execute({
targetProjectPath: cfg.targetProjectPath, targetProjectPath: cfg.targetProjectPath,
referenceProjectPath: cfg.referenceProjectPath, referenceProjectPath: cfg.referenceProjectPath,
gatherFilesConfig: cfg.gatherFilesConfigReference, gatherFilesConfig: cfg.gatherFilesConfig,
gatherFilesConfigReference: cfg.gatherFilesConfigReference,
}); });
// [A2] // [A2]
@ -434,6 +435,7 @@ class MatchPathsAnalyzer extends Analyzer {
/** @type {FindExportsAnalyzerResult} */ /** @type {FindExportsAnalyzerResult} */
const targetExportsResult = await targetFindExportsAnalyzer.execute({ const targetExportsResult = await targetFindExportsAnalyzer.execute({
targetProjectPath: cfg.targetProjectPath, targetProjectPath: cfg.targetProjectPath,
gatherFilesConfig: cfg.gatherFilesConfig,
}); });
// [A3] // [A3]
@ -441,6 +443,7 @@ class MatchPathsAnalyzer extends Analyzer {
/** @type {FindExportsAnalyzerResult} */ /** @type {FindExportsAnalyzerResult} */
const refFindExportsResult = await refFindExportsAnalyzer.execute({ const refFindExportsResult = await refFindExportsAnalyzer.execute({
targetProjectPath: cfg.referenceProjectPath, targetProjectPath: cfg.referenceProjectPath,
gatherFilesConfig: cfg.gatherFilesConfigReference,
}); });
/** /**
@ -448,14 +451,14 @@ class MatchPathsAnalyzer extends Analyzer {
* Automatically generate a mapping from lion docs import paths to extension layer * Automatically generate a mapping from lion docs import paths to extension layer
* import paths. To be served to extend-docs * import paths. To be served to extend-docs
* *
* [1] Find path variable.to 'WolfCheckbox' * [B1] Find path variable.to 'WolfCheckbox'
* Run 'match-subclasses' for target project: we find the 'rootFilePath' of class definition, * Run 'match-subclasses' for target project: we find the 'rootFilePath' of class definition,
* Result: './packages/wolf-checkbox/WolfCheckbox.js' * Result: './packages/wolf-checkbox/WolfCheckbox.js'
* [B1] Find export path of 'wolf-checkbox' * [B2] Find export path of 'wolf-checkbox'
* Run 'find-customelements' on target project and match rootFile of [A1] with rootFile of * Run 'find-customelements' on target project and match rootFile of [B1] with rootFile of
* constructor. * constructor.
* Result: './wolf-checkbox.js' * Result: './wolf-checkbox.js'
* [B2] Find export path of 'lion-checkbox' * [B3] Find export path of 'lion-checkbox'
* Run 'find-customelements' and find-exports (for rootpath) on reference project and match * Run 'find-customelements' and find-exports (for rootpath) on reference project and match
* rootFile of constructor with rootFiles of where LionCheckbox is defined. * rootFile of constructor with rootFiles of where LionCheckbox is defined.
* Result: './packages/checkbox/lion-checkbox.js', * Result: './packages/checkbox/lion-checkbox.js',
@ -467,6 +470,7 @@ class MatchPathsAnalyzer extends Analyzer {
/** @type {FindCustomelementsAnalyzerResult} */ /** @type {FindCustomelementsAnalyzerResult} */
const targetFindCustomelementsResult = await targetFindCustomelementsAnalyzer.execute({ const targetFindCustomelementsResult = await targetFindCustomelementsAnalyzer.execute({
targetProjectPath: cfg.targetProjectPath, targetProjectPath: cfg.targetProjectPath,
gatherFilesConfig: cfg.gatherFilesConfig,
}); });
// [B2] // [B2]
@ -474,6 +478,7 @@ class MatchPathsAnalyzer extends Analyzer {
/** @type {FindCustomelementsAnalyzerResult} */ /** @type {FindCustomelementsAnalyzerResult} */
const refFindCustomelementsResult = await refFindCustomelementsAnalyzer.execute({ const refFindCustomelementsResult = await refFindCustomelementsAnalyzer.execute({
targetProjectPath: cfg.referenceProjectPath, targetProjectPath: cfg.referenceProjectPath,
gatherFilesConfig: cfg.gatherFilesConfigReference,
}); });
// refFindExportsAnalyzer was already created in A3 // refFindExportsAnalyzer was already created in A3
@ -483,7 +488,6 @@ class MatchPathsAnalyzer extends Analyzer {
let queryOutput = matchPathsPostprocess( let queryOutput = matchPathsPostprocess(
targetMatchSubclassesResult, targetMatchSubclassesResult,
targetExportsResult, targetExportsResult,
// refImportsResult,
targetFindCustomelementsResult, targetFindCustomelementsResult,
refFindCustomelementsResult, refFindCustomelementsResult,
refFindExportsResult, refFindExportsResult,

View file

@ -1,7 +1,7 @@
/** /**
* @desc Readable way to do an async forEach * @desc Readable way to do an async forEach
* Since predictability mathers, all array items will be handled in a queue; * Since predictability matters, all array items will be handled in a queue,
* one after anotoher * one after another
* @param {array} array * @param {array} array
* @param {function} callback * @param {function} callback
*/ */
@ -13,8 +13,8 @@ async function aForEach(array, callback) {
} }
/** /**
* @desc Readable way to do an async forEach * @desc Readable way to do an async forEach
* Since predictability mathers, all array items will be handled in a queue; * If predictability does not matter, this method will traverse array items concurrently,
* one after anotoher * leading to a better performance
* @param {array} array * @param {array} array
* @param {function} callback * @param {function} callback
*/ */
@ -23,7 +23,7 @@ async function aForEachNonSequential(array, callback) {
} }
/** /**
* @desc Readable way to do an async map * @desc Readable way to do an async map
* Since predictability is crucial for a map, all array items will be handled in a queue; * Since predictability is crucial for a map, all array items will be handled in a queue,
* one after anotoher * one after anotoher
* @param {array} array * @param {array} array
* @param {function} callback * @param {function} callback

View file

@ -1,6 +1,7 @@
const sinon = require('sinon'); const sinon = require('sinon');
const pathLib = require('path'); const pathLib = require('path');
const { expect } = require('chai'); const { expect } = require('chai');
const commander = require('commander');
const { const {
mockProject, mockProject,
restoreMockedProjects, restoreMockedProjects,
@ -13,147 +14,339 @@ const {
suppressNonCriticalLogs, suppressNonCriticalLogs,
restoreSuppressNonCriticalLogs, restoreSuppressNonCriticalLogs,
} = require('../../test-helpers/mock-log-service-helpers.js'); } = require('../../test-helpers/mock-log-service-helpers.js');
const { InputDataService } = require('../../src/program/services/InputDataService.js');
const { QueryService } = require('../../src/program/services/QueryService.js'); const { QueryService } = require('../../src/program/services/QueryService.js');
const providenceModule = require('../../src/program/providence.js'); const providenceModule = require('../../src/program/providence.js');
const extendDocsModule = require('../../src/cli/generate-extend-docs-data.js'); const extendDocsModule = require('../../src/cli/generate-extend-docs-data.js');
const cliHelpersModule = require('../../src/cli/cli-helpers.js');
const dummyAnalyzer = require('../../test-helpers/templates/analyzer-template.js');
const { cli } = require('../../src/cli/cli.js'); const { cli } = require('../../src/cli/cli.js');
const { pathsArrayFromCs } = require('../../src/cli/cli-helpers.js'); const promptAnalyzerModule = require('../../src/cli/prompt-analyzer-menu.js');
const {
pathsArrayFromCs,
pathsArrayFromCollectionName,
appendProjectDependencyPaths,
} = cliHelpersModule;
const queryResults = []; const queryResults = [];
const rootDir = pathLib.resolve(__dirname, '../../'); const rootDir = pathLib.resolve(__dirname, '../../');
const externalCfgMock = {
searchTargetCollections: {
'lion-collection': [
'./providence-input-data/search-targets/example-project-a',
'./providence-input-data/search-targets/example-project-b',
// ...etc
],
},
referenceCollections: {
'lion-based-ui-collection': [
'./providence-input-data/references/lion-based-ui',
'./providence-input-data/references/lion-based-ui-labs',
],
},
};
async function runCli(args, cwd) {
process.argv = [...process.argv.slice(0, 2), ...args.split(' ')];
await cli({ cwd });
}
describe('Providence CLI', () => { describe('Providence CLI', () => {
let providenceStub;
let promptCfgStub;
let iExtConfStub;
let promptStub;
let qConfStub;
before(() => { before(() => {
suppressNonCriticalLogs(); // Prevent MaxListenersExceededWarning
commander.setMaxListeners(100);
mockWriteToJson(queryResults); mockWriteToJson(queryResults);
suppressNonCriticalLogs();
mockProject( mockProject(
{ {
'./src/OriginalComp.js': `export class OriginalComp {}`, './src/OriginalComp.js': `export class OriginalComp {}`,
'./src/inbetween.js': `export { OriginalComp as InBetweenComp } from './OriginalComp.js'`, './src/inbetween.js': `export { OriginalComp as InBetweenComp } from './OriginalComp.js'`,
'./index.js': `export { InBetweenComp as MyComp } from './src/inbetween.js'`, './index.js': `export { InBetweenComp as MyComp } from './src/inbetween.js'`,
'./node_modules/dependency-a/index.js': '',
'./bower_components/dependency-b/index.js': '',
}, },
{ {
projectName: 'example-project', projectName: 'example-project',
projectPath: '/mocked/path/example-project', projectPath: '/mocked/path/example-project',
}, },
); );
providenceStub = sinon.stub(providenceModule, 'providence').returns(
new Promise(resolve => {
resolve();
}),
);
promptCfgStub = sinon
.stub(promptAnalyzerModule, 'promptAnalyzerConfigMenu')
.returns({ analyzerConfig: { con: 'fig' } });
iExtConfStub = sinon.stub(InputDataService, 'getExternalConfig').returns(externalCfgMock);
promptStub = sinon
.stub(promptAnalyzerModule, 'promptAnalyzerMenu')
.returns({ analyzerName: 'mock-analyzer' });
qConfStub = sinon.stub(QueryService, 'getQueryConfigFromAnalyzer').returns({
analyzer: {
name: 'mock-analyzer',
requiresReference: true,
},
});
}); });
after(() => { after(() => {
commander.setMaxListeners(10);
restoreSuppressNonCriticalLogs(); restoreSuppressNonCriticalLogs();
restoreWriteToJson();
restoreMockedProjects(); restoreMockedProjects();
}); restoreWriteToJson();
let providenceStub;
let qConfStub;
beforeEach(() => {
qConfStub = sinon.stub(QueryService, 'getQueryConfigFromAnalyzer').returns({ analyzer: {} });
providenceStub = sinon.stub(providenceModule, 'providence').returns(Promise.resolve());
});
afterEach(() => {
providenceStub.restore(); providenceStub.restore();
promptCfgStub.restore();
iExtConfStub.restore();
promptStub.restore();
qConfStub.restore(); qConfStub.restore();
}); });
async function runCli(args, cwd) { afterEach(() => {
process.argv = [...process.argv.slice(0, 2), ...args.split(' ')]; providenceStub.resetHistory();
await cli({ cwd, addProjectDependencyPaths: false }); promptCfgStub.resetHistory();
} iExtConfStub.resetHistory();
promptStub.resetHistory();
const analyzCmd = 'analyze find-exports'; qConfStub.resetHistory();
it('creates a QueryConfig', async () => {
await runCli('analyze find-exports -t /mocked/path/example-project');
expect(qConfStub.called).to.be.true;
expect(qConfStub.args[0][0]).to.equal('find-exports');
}); });
const analyzeCmd = 'analyze mock-analyzer';
it('calls providence', async () => { it('calls providence', async () => {
await runCli(`${analyzCmd} -t /mocked/path/example-project`); await runCli(`${analyzeCmd} -t /mocked/path/example-project`);
expect(providenceStub.called).to.be.true; expect(providenceStub.called).to.be.true;
}); });
it('creates a QueryConfig', async () => {
await runCli(`${analyzeCmd} -t /mocked/path/example-project`);
expect(qConfStub.called).to.be.true;
expect(qConfStub.args[0][0]).to.equal('mock-analyzer');
});
describe('Global options', () => { describe('Global options', () => {
let pathsArrayFromCollectionStub;
let pathsArrayFromCsStub;
let appendProjectDependencyPathsStub;
before(() => {
pathsArrayFromCsStub = sinon
.stub(cliHelpersModule, 'pathsArrayFromCs')
.returns(['/mocked/path/example-project']);
pathsArrayFromCollectionStub = sinon
.stub(cliHelpersModule, 'pathsArrayFromCollectionName')
.returns(['/mocked/path/example-project']);
appendProjectDependencyPathsStub = sinon
.stub(cliHelpersModule, 'appendProjectDependencyPaths')
.returns([
'/mocked/path/example-project',
'/mocked/path/example-project/node_modules/mock-dep-a',
'/mocked/path/example-project/bower_components/mock-dep-b',
]);
});
after(() => {
pathsArrayFromCsStub.restore();
pathsArrayFromCollectionStub.restore();
appendProjectDependencyPathsStub.restore();
});
afterEach(() => {
pathsArrayFromCsStub.resetHistory();
pathsArrayFromCollectionStub.resetHistory();
appendProjectDependencyPathsStub.resetHistory();
});
it('"-e --extensions"', async () => { it('"-e --extensions"', async () => {
await runCli(`${analyzCmd} --extensions bla,blu`); await runCli(`${analyzeCmd} -e bla,blu`);
expect(providenceStub.args[0][1].gatherFilesConfig.extensions).to.eql(['.bla', '.blu']);
providenceStub.resetHistory();
await runCli(`${analyzeCmd} --extensions bla,blu`);
expect(providenceStub.args[0][1].gatherFilesConfig.extensions).to.eql(['.bla', '.blu']); expect(providenceStub.args[0][1].gatherFilesConfig.extensions).to.eql(['.bla', '.blu']);
}); });
describe('"-t", "--search-target-paths"', async () => { it('"-t --search-target-paths"', async () => {
it('allows absolute paths', async () => { await runCli(`${analyzeCmd} -t /mocked/path/example-project`, rootDir);
await runCli(`${analyzCmd} -t /mocked/path/example-project`, rootDir); expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].targetProjectPaths).to.eql([ expect(providenceStub.args[0][1].targetProjectPaths).to.eql(['/mocked/path/example-project']);
'/mocked/path/example-project',
]);
});
it('allows relative paths', async () => { pathsArrayFromCsStub.resetHistory();
await runCli( providenceStub.resetHistory();
`${analyzCmd} -t ./test-helpers/project-mocks/importing-target-project`,
rootDir,
);
expect(providenceStub.args[0][1].targetProjectPaths).to.eql([
`${rootDir}/test-helpers/project-mocks/importing-target-project`,
]);
await runCli( await runCli(`${analyzeCmd} --search-target-paths /mocked/path/example-project`, rootDir);
`${analyzCmd} -t test-helpers/project-mocks/importing-target-project`, expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
rootDir, expect(providenceStub.args[0][1].targetProjectPaths).to.eql(['/mocked/path/example-project']);
);
expect(providenceStub.args[0][1].targetProjectPaths).to.eql([
`${rootDir}/test-helpers/project-mocks/importing-target-project`,
]);
});
// TODO: globbing via cli-helpers doesn't work for some reason when run in this test
it.skip('allows globs', async () => {
await runCli(`${analyzCmd} -t test-helpers/*`, rootDir);
expect(providenceStub.args[0][1].targetProjectPaths).to.eql([
`${process.cwd()}/needed-for-test/pass-glob`,
]);
});
}); });
it('"-r", "--reference-paths"', async () => {}); it('"-r --reference-paths"', async () => {
it('"--search-target-collection"', async () => {}); await runCli(`${analyzeCmd} -r /mocked/path/example-project`, rootDir);
it('"--reference-collection"', async () => {}); expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].referenceProjectPaths).to.eql([
'/mocked/path/example-project',
]);
it.skip('"-R --verbose-report"', async () => {}); pathsArrayFromCsStub.resetHistory();
it.skip('"-D", "--debug"', async () => {}); providenceStub.resetHistory();
await runCli(`${analyzeCmd} --reference-paths /mocked/path/example-project`, rootDir);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].referenceProjectPaths).to.eql([
'/mocked/path/example-project',
]);
});
it('"--search-target-collection"', async () => {
await runCli(`${analyzeCmd} --search-target-collection lion-collection`, rootDir);
expect(pathsArrayFromCollectionStub.args[0][0]).to.equal('lion-collection');
expect(providenceStub.args[0][1].targetProjectPaths).to.eql(['/mocked/path/example-project']);
});
it('"--reference-collection"', async () => {
await runCli(`${analyzeCmd} --reference-collection lion-based-ui-collection`, rootDir);
expect(pathsArrayFromCollectionStub.args[0][0]).to.equal('lion-based-ui-collection');
expect(providenceStub.args[0][1].referenceProjectPaths).to.eql([
'/mocked/path/example-project',
]);
});
it('"-w --whitelist"', async () => {
await runCli(`${analyzeCmd} -w /mocked/path/example-project`, rootDir);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].gatherFilesConfig.filter).to.eql([
'/mocked/path/example-project',
]);
pathsArrayFromCsStub.resetHistory();
providenceStub.resetHistory();
await runCli(`${analyzeCmd} --whitelist /mocked/path/example-project`, rootDir);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].gatherFilesConfig.filter).to.eql([
'/mocked/path/example-project',
]);
});
it('"--whitelist-reference"', async () => {
await runCli(`${analyzeCmd} --whitelist-reference /mocked/path/example-project`, rootDir);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].gatherFilesConfigReference.filter).to.eql([
'/mocked/path/example-project',
]);
});
it('"-D --debug"', async () => {
await runCli(`${analyzeCmd} -D`, rootDir);
expect(providenceStub.args[0][1].debugEnabled).to.equal(true);
providenceStub.resetHistory();
await runCli(`${analyzeCmd} --debug`, rootDir);
expect(providenceStub.args[0][1].debugEnabled).to.equal(true);
});
it('--write-log-file"', async () => {
await runCli(`${analyzeCmd} --write-log-file`, rootDir);
expect(providenceStub.args[0][1].writeLogFile).to.equal(true);
});
it('--target-dependencies"', async () => {
await runCli(`${analyzeCmd}`, rootDir);
expect(appendProjectDependencyPathsStub.called).to.be.false;
appendProjectDependencyPathsStub.resetHistory();
providenceStub.resetHistory();
await runCli(`${analyzeCmd} --target-dependencies`, rootDir);
expect(appendProjectDependencyPathsStub.called).to.be.true;
expect(providenceStub.args[0][1].targetProjectPaths).to.eql([
'/mocked/path/example-project',
'/mocked/path/example-project/node_modules/mock-dep-a',
'/mocked/path/example-project/bower_components/mock-dep-b',
]);
});
it('--target-dependencies /^with-regex/"', async () => {
await runCli(`${analyzeCmd} --target-dependencies /^mock-/`, rootDir);
expect(appendProjectDependencyPathsStub.args[0][1]).to.equal('/^mock-/');
});
}); });
describe('Commands', () => { describe('Commands', () => {
describe('Analyze', () => { describe('Analyze', () => {
it('calls providence', async () => { it('calls providence', async () => {
expect(typeof dummyAnalyzer.name).to.equal('string'); await runCli(`${analyzeCmd}`, rootDir);
expect(providenceStub.called).to.be.true;
}); });
describe('Options', () => { describe('Options', () => {
it('"-o", "--prompt-optional-config"', async () => {}); it('"-o --prompt-optional-config"', async () => {
it('"-c", "--config"', async () => {}); await runCli(`analyze -o`, rootDir);
expect(promptStub.called).to.be.true;
promptStub.resetHistory();
await runCli(`analyze --prompt-optional-config`, rootDir);
expect(promptStub.called).to.be.true;
});
it('"-c --config"', async () => {
await runCli(`analyze mock-analyzer -c {"a":"2"}`, rootDir);
expect(qConfStub.args[0][0]).to.equal('mock-analyzer');
expect(qConfStub.args[0][1]).to.eql({ a: '2', metaConfig: undefined });
qConfStub.resetHistory();
await runCli(`analyze mock-analyzer --config {"a":"2"}`, rootDir);
expect(qConfStub.args[0][0]).to.equal('mock-analyzer');
expect(qConfStub.args[0][1]).to.eql({ a: '2', metaConfig: undefined });
});
it('calls "promptAnalyzerConfigMenu" without config given', async () => {
await runCli(`analyze mock-analyzer`, rootDir);
expect(promptCfgStub.called).to.be.true;
});
}); });
}); });
describe('Query', () => {});
describe('Search', () => {}); describe.skip('Query', () => {});
describe.skip('Search', () => {});
describe('Manage', () => {}); describe('Manage', () => {});
describe('Extend docs', () => { describe('Extend docs', () => {
let extendDocsStub; let extendDocsStub;
beforeEach(() => {
before(() => {
extendDocsStub = sinon extendDocsStub = sinon
.stub(extendDocsModule, 'launchProvidenceWithExtendDocs') .stub(extendDocsModule, 'launchProvidenceWithExtendDocs')
.returns(Promise.resolve()); .returns(Promise.resolve());
}); });
afterEach(() => { after(() => {
extendDocsStub.restore(); extendDocsStub.restore();
}); });
afterEach(() => {
extendDocsStub.resetHistory();
});
it('allows configuration', async () => { it('allows configuration', async () => {
await runCli( await runCli(
[ [
@ -176,8 +369,8 @@ describe('Providence CLI', () => {
}, },
outputFolder: '/outp', outputFolder: '/outp',
extensions: ['.bla'], extensions: ['.bla'],
whitelist: [`${process.cwd()}/wl`], whitelist: [`${rootDir}/wl`],
whitelistReference: [`${process.cwd()}/wlr`], whitelistReference: [`${rootDir}/wlr`],
}); });
}); });
}); });
@ -203,8 +396,102 @@ describe('CLI helpers', () => {
it('allows globs', async () => { it('allows globs', async () => {
expect(pathsArrayFromCs('test-helpers/project-mocks*', rootDir)).to.eql([ expect(pathsArrayFromCs('test-helpers/project-mocks*', rootDir)).to.eql([
`${process.cwd()}/test-helpers/project-mocks`, `${rootDir}/test-helpers/project-mocks`,
`${process.cwd()}/test-helpers/project-mocks-analyzer-outputs`, `${rootDir}/test-helpers/project-mocks-analyzer-outputs`,
]);
});
it('allows multiple comma separated paths', async () => {
const paths =
'test-helpers/project-mocks*, ./test-helpers/project-mocks/importing-target-project,/mocked/path/example-project';
expect(pathsArrayFromCs(paths, rootDir)).to.eql([
`${rootDir}/test-helpers/project-mocks`,
`${rootDir}/test-helpers/project-mocks-analyzer-outputs`,
`${rootDir}/test-helpers/project-mocks/importing-target-project`,
'/mocked/path/example-project',
]);
});
});
describe('pathsArrayFromCollectionName', () => {
it('gets collections from external target config', async () => {
expect(
pathsArrayFromCollectionName('lion-collection', 'search-target', externalCfgMock, rootDir),
).to.eql(
externalCfgMock.searchTargetCollections['lion-collection'].map(p =>
pathLib.join(rootDir, p),
),
);
});
it('gets collections from external reference config', async () => {
expect(
pathsArrayFromCollectionName(
'lion-based-ui-collection',
'reference',
externalCfgMock,
rootDir,
),
).to.eql(
externalCfgMock.referenceCollections['lion-based-ui-collection'].map(p =>
pathLib.join(rootDir, p),
),
);
});
});
describe('appendProjectDependencyPaths', () => {
before(() => {
mockWriteToJson(queryResults);
suppressNonCriticalLogs();
mockProject(
{
'./src/OriginalComp.js': `export class OriginalComp {}`,
'./src/inbetween.js': `export { OriginalComp as InBetweenComp } from './OriginalComp.js'`,
'./index.js': `export { InBetweenComp as MyComp } from './src/inbetween.js'`,
'./node_modules/dependency-a/index.js': '',
'./bower_components/dependency-b/index.js': '',
},
{
projectName: 'example-project',
projectPath: '/mocked/path/example-project',
},
);
});
it('adds bower and node dependencies', async () => {
const result = await appendProjectDependencyPaths(['/mocked/path/example-project']);
expect(result).to.eql([
'/mocked/path/example-project/node_modules/dependency-a',
'/mocked/path/example-project/bower_components/dependency-b',
'/mocked/path/example-project',
]);
});
it('allows a regex filter', async () => {
const result = await appendProjectDependencyPaths(['/mocked/path/example-project'], '/b$/');
expect(result).to.eql([
'/mocked/path/example-project/bower_components/dependency-b',
'/mocked/path/example-project',
]);
});
it('allows to filter out only npm or bower deps', async () => {
const result = await appendProjectDependencyPaths(['/mocked/path/example-project'], null, [
'npm',
]);
expect(result).to.eql([
'/mocked/path/example-project/node_modules/dependency-a',
'/mocked/path/example-project',
]);
const result2 = await appendProjectDependencyPaths(['/mocked/path/example-project'], null, [
'bower',
]);
expect(result2).to.eql([
'/mocked/path/example-project/bower_components/dependency-b',
'/mocked/path/example-project',
]); ]);
}); });
}); });

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

View file

@ -118,7 +118,7 @@ describe('InputDataService', () => {
]); ]);
}); });
it('allows passing excludeFolders', async () => { it('allows passing excluded folders', async () => {
const globOutput = InputDataService.gatherFilesFromDir('/fictional/project', { const globOutput = InputDataService.gatherFilesFromDir('/fictional/project', {
extensions: ['.html', '.js'], extensions: ['.html', '.js'],
filter: ['!nested/**'], filter: ['!nested/**'],