diff --git a/packages-node/providence-analytics/.gitignore b/packages-node/providence-analytics/.gitignore
index 7c9042dcc..854c4f41c 100644
--- a/packages-node/providence-analytics/.gitignore
+++ b/packages-node/providence-analytics/.gitignore
@@ -1,3 +1,3 @@
-providence-output
+/providence-output
providence-input-data
/.nyc_output
diff --git a/packages-node/providence-analytics/dashboard/src/app/components/p-table/PTable.js b/packages-node/providence-analytics/dashboard/app/components/p-table/PTable.js
similarity index 100%
rename from packages-node/providence-analytics/dashboard/src/app/components/p-table/PTable.js
rename to packages-node/providence-analytics/dashboard/app/components/p-table/PTable.js
diff --git a/packages-node/providence-analytics/dashboard/src/app/p-board.js b/packages-node/providence-analytics/dashboard/app/p-board.js
similarity index 97%
rename from packages-node/providence-analytics/dashboard/src/app/p-board.js
rename to packages-node/providence-analytics/dashboard/app/p-board.js
index f54922698..cdb55faea 100644
--- a/packages-node/providence-analytics/dashboard/src/app/p-board.js
+++ b/packages-node/providence-analytics/dashboard/app/p-board.js
@@ -278,7 +278,7 @@ class PBoard extends DecorateMixin(LitElement) {
if (!this.__menuData) {
return;
}
- await this.__fetchResults();
+ // await this.__fetchResults();
const elements = Array.from(this._selectionMenuFormNode.elements);
const repos = elements.filter(n => n.name === 'repos');
@@ -303,7 +303,8 @@ class PBoard extends DecorateMixin(LitElement) {
this.__providenceConf,
);
dataResult[i].type = specifierRes.exportSpecifier.name === '[file]' ? 'file' : 'specifier';
- dataResult[i].count = specifierRes.matchesPerProject
+ // dedupe, because outputs genarted with older versions might have dedupe problems
+ dataResult[i].count = Array.from(new Set(specifierRes.matchesPerProject))
.map(mpp => mpp.files)
.flat(Infinity).length;
dataResult[i].matchedProjects = specifierRes.matchesPerProject;
@@ -435,7 +436,7 @@ class PBoard extends DecorateMixin(LitElement) {
async __fetchMenuData() {
// Derived from providence.conf.js, generated in server.mjs
- this.__initialMenuData = await fetch('/menu-data').then(response => response.json());
+ this.__initialMenuData = await fetch('/menu-data.json').then(response => response.json());
}
async __fetchProvidenceConf() {
@@ -446,7 +447,7 @@ class PBoard extends DecorateMixin(LitElement) {
}
async __fetchResults() {
- this.__resultFiles = await fetch('/results').then(response => response.json());
+ this.__resultFiles = await fetch('/results.json').then(response => response.json());
}
}
customElements.define('p-board', PBoard);
diff --git a/packages-node/providence-analytics/dashboard/src/app/styles/global.css.js b/packages-node/providence-analytics/dashboard/app/styles/global.css.js
similarity index 100%
rename from packages-node/providence-analytics/dashboard/src/app/styles/global.css.js
rename to packages-node/providence-analytics/dashboard/app/styles/global.css.js
diff --git a/packages-node/providence-analytics/dashboard/src/app/styles/tableDecoration.css.js b/packages-node/providence-analytics/dashboard/app/styles/tableDecoration.css.js
similarity index 100%
rename from packages-node/providence-analytics/dashboard/src/app/styles/tableDecoration.css.js
rename to packages-node/providence-analytics/dashboard/app/styles/tableDecoration.css.js
diff --git a/packages-node/providence-analytics/dashboard/src/app/styles/tooltip.css.js b/packages-node/providence-analytics/dashboard/app/styles/tooltip.css.js
similarity index 100%
rename from packages-node/providence-analytics/dashboard/src/app/styles/tooltip.css.js
rename to packages-node/providence-analytics/dashboard/app/styles/tooltip.css.js
diff --git a/packages-node/providence-analytics/dashboard/src/app/styles/utils.css.js b/packages-node/providence-analytics/dashboard/app/styles/utils.css.js
similarity index 100%
rename from packages-node/providence-analytics/dashboard/src/app/styles/utils.css.js
rename to packages-node/providence-analytics/dashboard/app/styles/utils.css.js
diff --git a/packages-node/providence-analytics/dashboard/src/app/tooltipComponentStyles.js b/packages-node/providence-analytics/dashboard/app/tooltipComponentStyles.js
similarity index 100%
rename from packages-node/providence-analytics/dashboard/src/app/tooltipComponentStyles.js
rename to packages-node/providence-analytics/dashboard/app/tooltipComponentStyles.js
diff --git a/packages-node/providence-analytics/dashboard/src/app/utils/DecorateMixin.js b/packages-node/providence-analytics/dashboard/app/utils/DecorateMixin.js
similarity index 100%
rename from packages-node/providence-analytics/dashboard/src/app/utils/DecorateMixin.js
rename to packages-node/providence-analytics/dashboard/app/utils/DecorateMixin.js
diff --git a/packages-node/providence-analytics/dashboard/src/app/utils/GlobalDecorator.js b/packages-node/providence-analytics/dashboard/app/utils/GlobalDecorator.js
similarity index 100%
rename from packages-node/providence-analytics/dashboard/src/app/utils/GlobalDecorator.js
rename to packages-node/providence-analytics/dashboard/app/utils/GlobalDecorator.js
diff --git a/packages-node/providence-analytics/dashboard/src/app/utils/downloadFile.js b/packages-node/providence-analytics/dashboard/app/utils/downloadFile.js
similarity index 100%
rename from packages-node/providence-analytics/dashboard/src/app/utils/downloadFile.js
rename to packages-node/providence-analytics/dashboard/app/utils/downloadFile.js
diff --git a/packages-node/providence-analytics/dashboard/index.html b/packages-node/providence-analytics/dashboard/index.html
new file mode 100644
index 000000000..b76214248
--- /dev/null
+++ b/packages-node/providence-analytics/dashboard/index.html
@@ -0,0 +1,16 @@
+
+
+
+ Providence dashboard
+
+
+
+
+
+
+
+
diff --git a/packages-node/providence-analytics/dashboard/src/server.mjs b/packages-node/providence-analytics/dashboard/server.mjs
similarity index 78%
rename from packages-node/providence-analytics/dashboard/src/server.mjs
rename to packages-node/providence-analytics/dashboard/server.mjs
index b766d9c9b..4fd63a77f 100644
--- a/packages-node/providence-analytics/dashboard/src/server.mjs
+++ b/packages-node/providence-analytics/dashboard/server.mjs
@@ -1,9 +1,9 @@
import fs from 'fs';
import pathLib, { dirname } from 'path';
import { fileURLToPath } from 'url';
-import { createConfig, startServer } from 'es-dev-server';
-import { ReportService } from '../../src/program/services/ReportService.js';
-import { getProvidenceConf } from '../../src/program/utils/get-providence-conf.mjs';
+import { startDevServer } from '@web/dev-server';
+import { ReportService } from '../src/program/core/ReportService.js';
+import { providenceConfUtil } from '../src/program/utils/providence-conf-util.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -66,7 +66,7 @@ function createMiddleWares({ providenceConf, providenceConfRaw, searchTargetDeps
/**
* @param {object[]} collections
- * @returns {{[keu as string]: }}
+ * @returns {{[key as string]: }}
*/
function transformToProjectNames(collections) {
const res = {};
@@ -74,7 +74,7 @@ function createMiddleWares({ providenceConf, providenceConfRaw, searchTargetDeps
Object.entries(collections).map(([key, val]) => {
res[key] = val.map(c => {
const pkg = getPackageJson(c);
- return pkg && pkg.name;
+ return pkg?.name;
});
});
return res;
@@ -94,9 +94,10 @@ function createMiddleWares({ providenceConf, providenceConfRaw, searchTargetDeps
ctx.url = `${pathFromServerRootToHere}/index.html`;
return next();
}
- if (ctx.url === '/results') {
+ if (ctx.url === '/results.json') {
+ ctx.type = 'application/json';
ctx.body = resultFiles;
- } else if (ctx.url === '/menu-data') {
+ } else if (ctx.url === '/menu-data.json') {
// Gathers all data that are relevant to create a configuration menu
// at the top of the dashboard:
// - referenceCollections as defined in providence.conf.js
@@ -112,16 +113,18 @@ function createMiddleWares({ providenceConf, providenceConfRaw, searchTargetDeps
}
const menuData = {
- // N.B. theoratically there can be a mismatch between basename and pkgJson.name,
+ // N.B. theoretically there can be a mismatch between basename and pkgJson.name,
// but we assume folder names and pkgJson.names to be similar
searchTargetCollections,
referenceCollections: transformToProjectNames(providenceConf.referenceCollections),
searchTargetDeps,
};
+
+ ctx.type = 'application/json';
ctx.body = menuData;
} else if (ctx.url === '/providence-conf.js') {
- // Alloes frontend dasbboard app to find categoriesand other configs
- ctx.type = 'text/javascript';
+ // Allows frontend dasbboard app to find categories and other configs
+ ctx.type = 'application/javascript';
ctx.body = providenceConfRaw;
} else {
await next();
@@ -130,8 +133,8 @@ function createMiddleWares({ providenceConf, providenceConfRaw, searchTargetDeps
];
}
-(async function main() {
- const { providenceConf, providenceConfRaw } = await getProvidenceConf();
+export async function createDashboardServerConfig() {
+ const { providenceConf, providenceConfRaw } = (await providenceConfUtil.getConf()) || {};
const { searchTargetDeps, resultFiles } = await getCachedProvidenceResults();
// Needed for dev purposes (we call it from ./packages-node/providence-analytics/ instead of ./)
@@ -139,21 +142,37 @@ function createMiddleWares({ providenceConf, providenceConfRaw, searchTargetDeps
const fromPackageRoot = process.argv.includes('--serve-from-package-root');
const moduleRoot = fromPackageRoot ? pathLib.resolve(process.cwd(), '../../') : process.cwd();
- const config = createConfig({
- port: 8080,
+ return {
appIndex: pathLib.resolve(__dirname, 'index.html'),
rootDir: moduleRoot,
nodeResolve: true,
moduleDirs: pathLib.resolve(moduleRoot, 'node_modules'),
watch: false,
open: true,
- middlewares: createMiddleWares({
+ middleware: createMiddleWares({
providenceConf,
providenceConfRaw,
searchTargetDeps,
resultFiles,
}),
- });
+ };
+}
- await startServer(config);
+let resolveLoaded;
+export const serverInstanceLoaded = new Promise(resolve => {
+ resolveLoaded = resolve;
+});
+
+// Export interface as object, so we can mock it easily inside tests
+export const dashboardServer = {
+ start: async () => {
+ await startDevServer({ config: await createDashboardServerConfig() });
+ resolveLoaded();
+ },
+};
+
+(async () => {
+ if (process.argv.includes('--run-server')) {
+ dashboardServer.start();
+ }
})();
diff --git a/packages-node/providence-analytics/dashboard/src/index.html b/packages-node/providence-analytics/dashboard/src/index.html
deleted file mode 100644
index 75d37143f..000000000
--- a/packages-node/providence-analytics/dashboard/src/index.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
- providence-board
-
-
-
-
-
-
-
-
-
diff --git a/packages-node/providence-analytics/dashboard/src/server.js b/packages-node/providence-analytics/dashboard/src/server.js
deleted file mode 100644
index e5a8ecaa6..000000000
--- a/packages-node/providence-analytics/dashboard/src/server.js
+++ /dev/null
@@ -1,8 +0,0 @@
-const { LogService } = require('../../src/program/services/LogService.js');
-
-LogService.warn(
- 'Running via "dashboard/src/server.js" is deprecated. Please run "providence dashboard" instead.',
-);
-
-// @ts-ignore
-import('./server.mjs');
diff --git a/packages-node/providence-analytics/package.json b/packages-node/providence-analytics/package.json
index 9ac77e139..b8dbba319 100644
--- a/packages-node/providence-analytics/package.json
+++ b/packages-node/providence-analytics/package.json
@@ -26,14 +26,15 @@
"src"
],
"scripts": {
- "dashboard": "node ./dashboard/src/server.js --serve-from-package-root",
- "match-lion-imports": "npm run providence analyze match-imports --search-target-collection @lion-targets --reference-collection @lion-references",
+ "dashboard": "node ./dashboard/server.mjs --run-server --serve-from-package-root",
+ "postinstall": "npx patch-package",
+ "match-lion-imports": "npm run providence -- analyze match-subclasses --search-target-collection @lion-targets --reference-collection @lion-references --measure-perf --add-system-paths",
"providence": "node --max-old-space-size=8192 ./src/cli/index.mjs",
"publish-docs": "node ../../packages-node/publish-docs/src/cli.js --github-url https://github.com/ing-bank/lion/ --git-root-dir ../../",
"prepublishOnly": "npm run publish-docs",
- "test:node": "mocha './test-node/**/*.test.js'",
- "test:node:e2e": "mocha './test-node/program/**/*.e2e.js' --timeout 60000",
- "test:node:watch": "npm run test:node --watch"
+ "test:node": "npm run test:node:unit && npm run test:node:e2e",
+ "test:node:e2e": "mocha './test-node/**/*.e2e.{j,mj}s' --timeout 60000",
+ "test:node:unit": "mocha './test-node/**/*.test.{j,mj}s'"
},
"dependencies": {
"@babel/core": "^7.10.1",
@@ -44,26 +45,25 @@
"@babel/register": "^7.5.5",
"@babel/traverse": "^7.23.2",
"@babel/types": "^7.9.0",
- "@rollup/plugin-node-resolve": "^13.0.6",
- "@typescript-eslint/typescript-estree": "^3.0.0",
+ "@rollup/plugin-node-resolve": "^15.0.0",
+ "@web/dev-server": "^0.1.28",
"anymatch": "^3.1.1",
"chalk": "^4.1.0",
"commander": "^2.20.0",
"deepmerge": "^4.0.0",
- "es-dev-server": "^1.57.1",
- "es-module-lexer": "^0.3.6",
"glob": "^7.1.6",
- "htm": "^3.0.3",
"inquirer": "^7.0.0",
"is-negated-glob": "^1.0.0",
"lit-element": "~2.4.0",
- "mock-require": "^3.0.3",
- "ora": "^3.4.0",
"parse5": "^5.1.1",
"read-package-tree": "5.3.1",
"semver": "^7.5.2",
"typescript": "~4.8.4"
},
+ "devDependencies": {
+ "@web/dev-server-core": "^0.3.19",
+ "mock-require": "^3.0.3"
+ },
"keywords": [
"analysis",
"impact",
@@ -77,5 +77,8 @@
],
"publishConfig": {
"access": "public"
+ },
+ "imports": {
+ "#types": "./src/program/types"
}
}
diff --git a/packages-node/providence-analytics/patches/@web+dev-server-core+0.3.17.patch b/packages-node/providence-analytics/patches/@web+dev-server-core+0.3.17.patch
new file mode 100644
index 000000000..964b16886
--- /dev/null
+++ b/packages-node/providence-analytics/patches/@web+dev-server-core+0.3.17.patch
@@ -0,0 +1,11 @@
+diff --git a/node_modules/@web/dev-server-core/test-helpers.mjs b/node_modules/@web/dev-server-core/test-helpers.mjs
+index 1a4d604..9c0d714 100644
+--- a/node_modules/@web/dev-server-core/test-helpers.mjs
++++ b/node_modules/@web/dev-server-core/test-helpers.mjs
+@@ -1,5 +1,5 @@
+ // this file is autogenerated with the generate-mjs-dts-entrypoints script
+-import cjsEntrypoint from './dist/index.js';
++import cjsEntrypoint from './dist/test-helpers.js';
+
+ const {
+ virtualFilesPlugin,
diff --git a/packages-node/providence-analytics/providence.conf.mjs b/packages-node/providence-analytics/providence.conf.mjs
index d59e9fe30..69c877060 100644
--- a/packages-node/providence-analytics/providence.conf.mjs
+++ b/packages-node/providence-analytics/providence.conf.mjs
@@ -1,37 +1,4 @@
-import pathLib, { dirname } from 'path';
-import fs from 'fs';
-import { fileURLToPath } from 'url';
-
-const __dirname = dirname(fileURLToPath(import.meta.url));
-
-// This file is read by dashboard and cli and needs to be present under process.cwd()
-// It mainly serves as an example and it allows to run the dashboard locally
-// from within this repo.
-
-/**
- * @returns {string[]}
- */
-function getAllLionScopedPackagePaths() {
- const rootPath = pathLib.resolve(__dirname, '../../packages');
- const filesAndDirs = fs.readdirSync(rootPath);
- const packages = filesAndDirs.filter(f => {
- const filePath = pathLib.join(rootPath, f);
- if (fs.lstatSync(filePath).isDirectory()) {
- let pkgJson;
- try {
- pkgJson = JSON.parse(fs.readFileSync(pathLib.resolve(filePath, './package.json')));
- // eslint-disable-next-line no-empty
- } catch (_) {
- return false;
- }
- return pkgJson.name && pkgJson.name.startsWith('@lion/');
- }
- return false;
- });
- return packages.map(p => pathLib.join(rootPath, p));
-}
-
-const lionScopedPackagePaths = getAllLionScopedPackagePaths();
+const lionScopedPackagePaths = ['../../packages/ui'];
export default {
metaConfig: {
@@ -44,7 +11,9 @@ export default {
categories: {
overlays: localFilePath => {
const names = ['dialog', 'tooltip'];
- const fromPackages = names.some(p => localFilePath.startsWith(`./packages/${p}`));
+ const fromPackages = names.some(p =>
+ localFilePath.startsWith(`./packages/ui/components/${p}`),
+ );
const fromRoot =
names.some(p => localFilePath.startsWith(`./ui-${p}`)) ||
localFilePath.startsWith('./overlays.js');
@@ -65,6 +34,6 @@ export default {
// Usually the references are different from the targets.
// In this demo file, we test @lion usage amongst itself
// Select via " providence analyze --reference-collection 'exampleCollection' "
- '@lion-references': lionScopedPackagePaths,
+ '@lion-references': ['../../packages/ui/'],
},
};
diff --git a/packages-node/providence-analytics/src/cli/cli-helpers.js b/packages-node/providence-analytics/src/cli/cli-helpers.js
index 8667735f3..a72185ccc 100644
--- a/packages-node/providence-analytics/src/cli/cli-helpers.js
+++ b/packages-node/providence-analytics/src/cli/cli-helpers.js
@@ -3,19 +3,29 @@ const pathLib = require('path');
const child_process = require('child_process'); // eslint-disable-line camelcase
const glob = require('glob');
const readPackageTree = require('../program/utils/read-package-tree-with-bower-support.js');
-const { InputDataService } = require('../program/services/InputDataService.js');
-const { LogService } = require('../program/services/LogService.js');
-const { aForEach } = require('../program/utils/async-array-utils.js');
+const { LogService } = require('../program/core/LogService.js');
const { toPosixPath } = require('../program/utils/to-posix-path.js');
+/**
+ * @param {any[]} arr
+ * @returns {any[]}
+ */
function flatten(arr) {
return Array.prototype.concat.apply([], arr);
}
+/**
+ * @param {string} v
+ * @returns {string[]}
+ */
function csToArray(v) {
return v.split(',').map(v => v.trim());
}
+/**
+ * @param {string} v like 'js,html'
+ * @returns {string[]} like ['.js', '.html']
+ */
function extensionsFromCs(v) {
return csToArray(v).map(v => `.${v}`);
}
@@ -25,13 +35,13 @@ function setQueryMethod(m) {
if (allowedMehods.includes(m)) {
return m;
}
- // eslint-disable-next-line no-console
LogService.error(`Please provide one of the following methods: ${allowedMehods.join(', ')}`);
return undefined;
}
/**
- * @returns {string[]}
+ * @param {string} t
+ * @returns {string[]|undefined}
*/
function pathsArrayFromCs(t, cwd = process.cwd()) {
if (!t) {
@@ -57,27 +67,40 @@ function pathsArrayFromCs(t, cwd = process.cwd()) {
/**
* @param {string} name collection name found in eCfg
- * @param {'search-target'|'reference'} [colType='search-targets'] collection type
- * @param {object} eCfg external configuration. Usually providence.conf.js
- * @returns {string[]}
+ * @param {'search-target'|'reference'} collectionType collection type
+ * @param {{searchTargetCollections: {[repo:string]:string[]}; referenceCollections:{[repo:string]:string[]}}} [eCfg] external configuration. Usually providence.conf.js
+ * @param {string} [cwd]
+ * @returns {string[]|undefined}
*/
-function pathsArrayFromCollectionName(name, colType = 'search-target', eCfg, cwd) {
+function pathsArrayFromCollectionName(
+ name,
+ collectionType = 'search-target',
+ eCfg,
+ cwd = process.cwd(),
+) {
let collection;
- if (colType === 'search-target') {
- collection = eCfg.searchTargetCollections;
- } else if (colType === 'reference') {
- collection = eCfg.referenceCollections;
+ if (collectionType === 'search-target') {
+ collection = eCfg?.searchTargetCollections;
+ } else if (collectionType === 'reference') {
+ collection = eCfg?.referenceCollections;
}
- if (collection && collection[name]) {
+ if (collection?.[name]) {
return pathsArrayFromCs(collection[name].join(','), cwd);
}
return undefined;
}
+/**
+ * @param {string} processArgStr
+ * @param {object} [opts]
+ * @returns {Promise<{ code:string; number:string }>}
+ * @throws {Error}
+ */
function spawnProcess(processArgStr, opts) {
const processArgs = processArgStr.split(' ');
// eslint-disable-next-line camelcase
const proc = child_process.spawn(processArgs[0], processArgs.slice(1), opts);
+ /** @type {string} */
let output;
proc.stdout.on('data', data => {
output += data;
@@ -95,28 +118,26 @@ function spawnProcess(processArgStr, opts) {
}
/**
+ * When providence is called from the root of a repo and no target is provided,
+ * this will provide the default fallback (the project itself)
+ * @param {string} cwd
* @returns {string[]}
*/
-function targetDefault() {
- // eslint-disable-next-line import/no-dynamic-require, global-require
- const { name } = require(`${process.cwd()}/package.json`);
- if (name === 'providence') {
- return InputDataService.targetProjectPaths;
- }
- return [toPosixPath(process.cwd())];
+function targetDefault(cwd) {
+ return [toPosixPath(cwd)];
}
/**
- * @desc Returns all sub projects matching condition supplied in matchFn
- * @param {string[]} searchTargetPaths all search-target project paths
- * @param {string} matchPattern base for RegExp
- * @param {string[]} modes
+ * Returns all sub projects matching condition supplied in matchFn
+ * @param {string[]} rootPaths all search-target project paths
+ * @param {string} [matchPattern] base for RegExp
+ * @param {('npm'|'bower')[]} [modes]
*/
async function appendProjectDependencyPaths(rootPaths, matchPattern, modes = ['npm', 'bower']) {
let matchFn;
if (matchPattern) {
if (matchPattern.startsWith('/') && matchPattern.endsWith('/')) {
- matchFn = (_, d) => {
+ matchFn = (/** @type {any} */ _, /** @type {string} */ d) => {
const reString = matchPattern.slice(1, -1);
const result = new RegExp(reString).test(d);
LogService.debug(`[appendProjectDependencyPaths]: /${reString}/.test(${d} => ${result})`);
@@ -128,13 +149,14 @@ async function appendProjectDependencyPaths(rootPaths, matchPattern, modes = ['n
);
}
}
+ /** @type {string[]} */
const depProjectPaths = [];
- await aForEach(rootPaths, async targetPath => {
- await aForEach(modes, async mode => {
+ for (const targetPath of rootPaths) {
+ for (const mode of modes) {
await readPackageTree(
targetPath,
matchFn,
- (err, tree) => {
+ (/** @type {string | undefined} */ err, /** @type {{ children: any[]; }} */ tree) => {
if (err) {
throw new Error(err);
}
@@ -143,8 +165,8 @@ async function appendProjectDependencyPaths(rootPaths, matchPattern, modes = ['n
},
mode,
);
- });
- });
+ }
+ }
// Write all data to {outputPath}/projectDeps.json
// const projectDeps = {};
// rootPaths.forEach(rootP => {
@@ -154,25 +176,27 @@ async function appendProjectDependencyPaths(rootPaths, matchPattern, modes = ['n
return depProjectPaths.concat(rootPaths).map(toPosixPath);
}
+/**
+ * Will install all npm and bower deps, so an analysis can be performed on them as well.
+ * Relevant when '--target-dependencies' is supplied.
+ * @param {string[]} searchTargetPaths
+ */
async function installDeps(searchTargetPaths) {
- return aForEach(searchTargetPaths, async t => {
- const spawnConfig = { cwd: t };
- const extraOptions = { log: true };
-
- LogService.info(`Installing npm dependencies for ${pathLib.basename(t)}`);
+ for (const targetPath of searchTargetPaths) {
+ LogService.info(`Installing npm dependencies for ${pathLib.basename(targetPath)}`);
try {
- await spawnProcess('npm i --no-progress', spawnConfig, extraOptions);
+ await spawnProcess('npm i --no-progress', { cwd: targetPath });
} catch (e) {
LogService.error(e);
}
- LogService.info(`Installing bower dependencies for ${pathLib.basename(t)}`);
+ LogService.info(`Installing bower dependencies for ${pathLib.basename(targetPath)}`);
try {
- await spawnProcess(`bower i --production --force-latest`, spawnConfig, extraOptions);
+ await spawnProcess(`bower i --production --force-latest`, { cwd: targetPath });
} catch (e) {
LogService.error(e);
}
- });
+ }
}
module.exports = {
diff --git a/packages-node/providence-analytics/src/cli/cli.js b/packages-node/providence-analytics/src/cli/cli.mjs
similarity index 87%
rename from packages-node/providence-analytics/src/cli/cli.js
rename to packages-node/providence-analytics/src/cli/cli.mjs
index f7748d8c4..33f2b0288 100755
--- a/packages-node/providence-analytics/src/cli/cli.js
+++ b/packages-node/providence-analytics/src/cli/cli.mjs
@@ -1,21 +1,30 @@
-const child_process = require('child_process'); // eslint-disable-line camelcase
-const pathLib = require('path');
-const commander = require('commander');
-const providenceModule = require('../program/providence.js');
-const { LogService } = require('../program/services/LogService.js');
-const { QueryService } = require('../program/services/QueryService.js');
-const { InputDataService } = require('../program/services/InputDataService.js');
-const promptModule = require('./prompt-analyzer-menu.js');
-const cliHelpers = require('./cli-helpers.js');
-const extendDocsModule = require('./launch-providence-with-extend-docs.js');
-const { toPosixPath } = require('../program/utils/to-posix-path.js');
+import child_process from 'child_process'; // eslint-disable-line camelcase
+import pathLib from 'path';
+import fs from 'fs';
+import commander from 'commander';
+import providenceModule from '../program/providence.js';
+import { LogService } from '../program/core/LogService.js';
+import { QueryService } from '../program/core/QueryService.js';
+import { InputDataService } from '../program/core/InputDataService.js';
+import promptModule from './prompt-analyzer-menu.js';
+import cliHelpers from './cli-helpers.js';
+import extendDocsModule from './launch-providence-with-extend-docs.js';
+import { toPosixPath } from '../program/utils/to-posix-path.js';
+import { getCurrentDir } from '../program/utils/get-current-dir.mjs';
+import { dashboardServer } from '../../dashboard/server.mjs';
-const { extensionsFromCs, setQueryMethod, targetDefault, installDeps, spawnProcess } = cliHelpers;
+const { version } = JSON.parse(
+ fs.readFileSync(pathLib.resolve(getCurrentDir(import.meta.url), '../../package.json'), 'utf8'),
+);
+const { extensionsFromCs, setQueryMethod, targetDefault, installDeps } = cliHelpers;
-const { version } = require('../../package.json');
-
-async function cli({ cwd, providenceConf } = {}) {
+/**
+ * @param {{cwd?:string; argv: string[]; providenceConf?: object}} cfg
+ */
+export async function cli({ cwd = process.cwd(), providenceConf, argv = process.argv }) {
+ /** @type {(value: any) => void} */
let resolveCli;
+ /** @type {(reason?: any) => void} */
let rejectCli;
const cliPromise = new Promise((resolve, reject) => {
@@ -35,7 +44,7 @@ async function cli({ cwd, providenceConf } = {}) {
// TODO: change back to "InputDataService.getExternalConfig();" once full package ESM
const externalConfig = providenceConf;
- async function getQueryInputData(
+ async function getQueryConfigAndMeta(
/* eslint-disable no-shadow */
searchMode,
regexSearchOptions,
@@ -80,7 +89,7 @@ async function cli({ cwd, providenceConf } = {}) {
}
async function launchProvidence() {
- const { queryConfig, queryMethod } = await getQueryInputData(
+ const { queryConfig, queryMethod } = await getQueryConfigAndMeta(
searchMode,
regexSearchOptions,
featureOptions,
@@ -130,6 +139,8 @@ async function cli({ cwd, providenceConf } = {}) {
targetProjectRootPaths: searchTargetPaths,
writeLogFile: commander.writeLogFile,
skipCheckMatchCompatibility: commander.skipCheckMatchCompatibility,
+ measurePerformance: commander.measurePerf,
+ addSystemPathsInResult: commander.addSystemPaths,
});
}
@@ -153,15 +164,6 @@ async function cli({ cwd, providenceConf } = {}) {
}
}
- async function runDashboard() {
- const pathFromServerRootToDashboard = `${pathLib.relative(
- process.cwd(),
- pathLib.resolve(__dirname, '../../dashboard'),
- )}`;
-
- spawnProcess(`node ${pathFromServerRootToDashboard}/src/server.mjs`);
- }
-
commander
.version(version, '-v, --version')
.option('-e, --extensions [extensions]', 'extensions like "js,html"', extensionsFromCs, [
@@ -174,7 +176,7 @@ async function cli({ cwd, providenceConf } = {}) {
`path(s) to project(s) on which analysis/querying should take place. Requires
a list of comma seperated values relative to project root`,
v => cliHelpers.pathsArrayFromCs(v, cwd),
- targetDefault(),
+ targetDefault(cwd),
)
.option(
'-r, --reference-paths [references]',
@@ -185,12 +187,12 @@ async function cli({ cwd, providenceConf } = {}) {
InputDataService.referenceProjectPaths,
)
.option('-a, --allowlist [allowlist]', `allowlisted paths, like 'src/**/*, packages/**/*'`, v =>
- cliHelpers.csToArray(v, cwd),
+ cliHelpers.csToArray(v),
)
.option(
'--allowlist-reference [allowlist-reference]',
`allowed paths for reference, like 'src/**/*, packages/**/*'`,
- v => cliHelpers.csToArray(v, cwd),
+ v => cliHelpers.csToArray(v),
)
.option(
'--search-target-collection [collection-name]',
@@ -232,7 +234,9 @@ async function cli({ cwd, providenceConf } = {}) {
.option(
'--skip-check-match-compatibility',
`skips semver checks, handy for forward compatible libs or libs below v1`,
- );
+ )
+ .option('--measure-perf', 'Logs the completion time in seconds')
+ .option('--add-system-paths', 'Adds system paths to results');
commander
.command('search ')
@@ -346,12 +350,10 @@ async function cli({ cwd, providenceConf } = {}) {
via providence.conf`,
)
.action(() => {
- runDashboard();
+ dashboardServer.start();
});
- commander.parse(process.argv);
+ commander.parse(argv);
await cliPromise;
}
-
-module.exports = { cli };
diff --git a/packages-node/providence-analytics/src/cli/index.mjs b/packages-node/providence-analytics/src/cli/index.mjs
index 32e76cb7e..a794b437c 100755
--- a/packages-node/providence-analytics/src/cli/index.mjs
+++ b/packages-node/providence-analytics/src/cli/index.mjs
@@ -1,9 +1,9 @@
#!/usr/bin/env node
-import { cli } from './cli.js';
-import { getProvidenceConf } from '../program/utils/get-providence-conf.mjs';
+import { cli } from './cli.mjs';
+import { providenceConfUtil } from '../program/utils/providence-conf-util.mjs';
(async () => {
// We need to provide config to cli, until whole package is rewritten as ESM.
- const { providenceConf } = (await getProvidenceConf()) || {};
+ const { providenceConf } = (await providenceConfUtil.getConf()) || {};
cli({ providenceConf });
})();
diff --git a/packages-node/providence-analytics/src/cli/launch-providence-with-extend-docs.js b/packages-node/providence-analytics/src/cli/launch-providence-with-extend-docs.js
index 1d521b6ec..9f411fdcb 100644
--- a/packages-node/providence-analytics/src/cli/launch-providence-with-extend-docs.js
+++ b/packages-node/providence-analytics/src/cli/launch-providence-with-extend-docs.js
@@ -3,11 +3,27 @@ const fs = require('fs');
const pathLib = require('path');
const { performance } = require('perf_hooks');
const providenceModule = require('../program/providence.js');
-const { QueryService } = require('../program/services/QueryService.js');
-const { InputDataService } = require('../program/services/InputDataService.js');
-const { LogService } = require('../program/services/LogService.js');
+const { QueryService } = require('../program/core/QueryService.js');
+const { InputDataService } = require('../program/core/InputDataService.js');
+const { LogService } = require('../program/core/LogService.js');
const { flatten } = require('./cli-helpers.js');
+/**
+ * @typedef {import('../program/types').PathFromSystemRoot} PathFromSystemRoot
+ * @typedef {import('../program/types').GatherFilesConfig} GatherFilesConfig
+ */
+
+/**
+ * @param {{
+ * referenceProjectPaths: PathFromSystemRoot[];
+ * prefixCfg:{from:string;to:string};
+ * extensions:GatherFilesConfig['extensions'];
+ * allowlist?:string[];
+ * allowlistReference?:string[];
+ * cwd:PathFromSystemRoot
+ * }} opts
+ * @returns
+ */
async function getExtendDocsResults({
referenceProjectPaths,
prefixCfg,
@@ -22,7 +38,7 @@ async function getExtendDocsResults({
QueryService.getQueryConfigFromAnalyzer('match-paths', { prefix: prefixCfg }),
{
gatherFilesConfig: {
- extensions: extensions || ['.js'],
+ extensions: extensions || /** @type {GatherFilesConfig['extensions']} */ (['.js']),
allowlist: allowlist || ['!coverage', '!test'],
},
gatherFilesConfigReference: {
@@ -31,7 +47,7 @@ async function getExtendDocsResults({
},
queryMethod: 'ast',
report: false,
- targetProjectPaths: [pathLib.resolve(cwd)],
+ targetProjectPaths: [cwd],
referenceProjectPaths,
// For mono repos, a match between root package.json and ref project will not exist.
// Disable this check, so it won't be a blocker for extendin docs
@@ -45,7 +61,7 @@ async function getExtendDocsResults({
/**
* @param {string} pathStr ./packages/lea-tabs/lea-tabs.js
- * @param {string[]} pkgs ['packages/lea-tabs', ...]
+ * @param {{path:string;name:string}[]} pkgs ['packages/lea-tabs', ...]
*/
function replaceToMonoRepoPath(pathStr, pkgs) {
let result = pathStr;
diff --git a/packages-node/providence-analytics/src/cli/prompt-analyzer-menu.js b/packages-node/providence-analytics/src/cli/prompt-analyzer-menu.js
index 8a9d18d1f..5bf53dc55 100644
--- a/packages-node/providence-analytics/src/cli/prompt-analyzer-menu.js
+++ b/packages-node/providence-analytics/src/cli/prompt-analyzer-menu.js
@@ -2,9 +2,9 @@ const fs = require('fs');
const pathLib = require('path');
const inquirer = require('inquirer');
const { default: traverse } = require('@babel/traverse');
-const { InputDataService } = require('../program/services/InputDataService.js');
-const { AstService } = require('../program/services/AstService.js');
-const { LogService } = require('../program/services/LogService.js');
+const { InputDataService } = require('../program/core/InputDataService.js');
+const { AstService } = require('../program/core/AstService.js');
+const { LogService } = require('../program/core/LogService.js');
const JsdocCommentParser = require('../program/utils/jsdoc-comment-parser.js');
/**
diff --git a/packages-node/providence-analytics/src/index.js b/packages-node/providence-analytics/src/index.js
index 237e0c755..366d509e8 100644
--- a/packages-node/providence-analytics/src/index.js
+++ b/packages-node/providence-analytics/src/index.js
@@ -1,7 +1,7 @@
const { providence } = require('./program/providence.js');
-const { QueryService } = require('./program/services/QueryService.js');
-const { LogService } = require('./program/services/LogService.js');
-const { InputDataService } = require('./program/services/InputDataService.js');
-const { AstService } = require('./program/services/AstService.js');
+const { QueryService } = require('./program/core/QueryService.js');
+const { LogService } = require('./program/core/LogService.js');
+const { InputDataService } = require('./program/core/InputDataService.js');
+const { AstService } = require('./program/core/AstService.js');
module.exports = { providence, QueryService, LogService, InputDataService, AstService };
diff --git a/packages-node/providence-analytics/src/program/analyzers/find-classes.js b/packages-node/providence-analytics/src/program/analyzers/find-classes.js
index 61546648f..64541a807 100644
--- a/packages-node/providence-analytics/src/program/analyzers/find-classes.js
+++ b/packages-node/providence-analytics/src/program/analyzers/find-classes.js
@@ -2,18 +2,21 @@
const pathLib = require('path');
const t = require('@babel/types');
const { default: traverse } = require('@babel/traverse');
-const { Analyzer } = require('./helpers/Analyzer.js');
+const { Analyzer } = require('../core/Analyzer.js');
const { trackDownIdentifierFromScope } = require('./helpers/track-down-identifier.js');
-const { aForEach } = require('../utils/async-array-utils.js');
-/** @typedef {import('../types/analyzers').FindClassesAnalyzerOutput} FindClassesAnalyzerOutput */
-/** @typedef {import('../types/analyzers').FindClassesAnalyzerOutputEntry} FindClassesAnalyzerOutputEntry */
-/** @typedef {import('../types/analyzers').FindClassesConfig} FindClassesConfig */
+/**
+ * @typedef {import('@babel/types').File} File
+ * @typedef {import('@babel/types').ClassMethod} ClassMethod
+ * @typedef {import('../types/analyzers').FindClassesAnalyzerOutput} FindClassesAnalyzerOutput
+ * @typedef {import('../types/analyzers').FindClassesAnalyzerOutputEntry} FindClassesAnalyzerOutputEntry
+ * @typedef {import('../types/analyzers').FindClassesConfig} FindClassesConfig
+ */
/**
* Finds import specifiers and sources
- * @param {BabelAst} ast
- * @param {string} relativePath the file being currently processed
+ * @param {File} ast
+ * @param {string} fullCurrentFilePath the file being currently processed
*/
async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
// The transformed entry
@@ -34,6 +37,10 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
return 'public';
}
+ /**
+ * @param {{node:ClassMethod}} cfg
+ * @returns
+ */
function isStaticProperties({ node }) {
return node.static && node.kind === 'get' && node.key.name === 'properties';
}
@@ -73,7 +80,12 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
// return false;
// }
- async function traverseClass(path, { isMixin } = {}) {
+ /**
+ *
+ * @param {*} path
+ * @param {{isMixin?:boolean}} param1
+ */
+ async function traverseClass(path, { isMixin = false } = {}) {
const classRes = {};
classRes.name = path.node.id && path.node.id.name;
classRes.isMixin = Boolean(isMixin);
@@ -95,7 +107,8 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
// or an external path like '@lion/overlays'. In the latter case,
// tracking down will halt and should be done when there is access to
// the external repo... (similar to how 'match-imports' analyzer works)
- await aForEach(superClasses, async classObj => {
+
+ for (const classObj of superClasses) {
// Finds the file that holds the declaration of the import
classObj.rootFile = await trackDownIdentifierFromScope(
path,
@@ -103,13 +116,17 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
fullCurrentFilePath,
projectPath,
);
- });
+ }
classRes.superClasses = superClasses;
}
- classRes.members = {};
- classRes.members.props = []; // meta: private, public, getter/setter, (found in static get properties)
- classRes.members.methods = []; // meta: private, public, getter/setter
+ classRes.members = {
+ // meta: private, public, getter/setter, (found in static get properties)
+ props: [],
+ // meta: private, public, getter/setter
+ methods: [],
+ };
+
path.traverse({
ClassMethod(path) {
// if (isBlacklisted(path)) {
@@ -174,9 +191,9 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
},
});
- await aForEach(classesToTraverse, async klass => {
+ for (const klass of classesToTraverse) {
await traverseClass(klass.path, { isMixin: klass.isMixin });
- });
+ }
return classesFound;
}
@@ -202,9 +219,8 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
// }
class FindClassesAnalyzer extends Analyzer {
- constructor() {
- super();
- this.name = 'find-classes';
+ static get analyzerName() {
+ return 'find-classes';
}
/**
diff --git a/packages-node/providence-analytics/src/program/analyzers/find-customelements.js b/packages-node/providence-analytics/src/program/analyzers/find-customelements.js
index d5702455d..4869bc611 100644
--- a/packages-node/providence-analytics/src/program/analyzers/find-customelements.js
+++ b/packages-node/providence-analytics/src/program/analyzers/find-customelements.js
@@ -1,9 +1,8 @@
const pathLib = require('path');
const t = require('@babel/types');
const { default: traverse } = require('@babel/traverse');
-const { Analyzer } = require('./helpers/Analyzer.js');
+const { Analyzer } = require('../core/Analyzer.js');
const { trackDownIdentifierFromScope } = require('./helpers/track-down-identifier.js');
-const { aForEach } = require('../utils/async-array-utils.js');
function cleanup(transformedEntry) {
transformedEntry.forEach(definitionObj => {
@@ -18,7 +17,7 @@ function cleanup(transformedEntry) {
async function trackdownRoot(transformedEntry, relativePath, projectPath) {
const fullCurrentFilePath = pathLib.resolve(projectPath, relativePath);
- await aForEach(transformedEntry, async definitionObj => {
+ for (const definitionObj of transformedEntry) {
const rootFile = await trackDownIdentifierFromScope(
definitionObj.__tmp.path,
definitionObj.constructorIdentifier,
@@ -27,7 +26,7 @@ async function trackdownRoot(transformedEntry, relativePath, projectPath) {
);
// eslint-disable-next-line no-param-reassign
definitionObj.rootFile = rootFile;
- });
+ }
return transformedEntry;
}
@@ -85,13 +84,12 @@ function findCustomElementsPerAstEntry(ast) {
}
class FindCustomelementsAnalyzer extends Analyzer {
- constructor() {
- super();
- this.name = 'find-customelements';
+ static get analyzerName() {
+ return 'find-customelements';
}
/**
- * @desc Finds export specifiers and sources
+ * Finds export specifiers and sources
* @param {FindCustomelementsConfig} customConfig
*/
async execute(customConfig = {}) {
diff --git a/packages-node/providence-analytics/src/program/analyzers/find-exports.js b/packages-node/providence-analytics/src/program/analyzers/find-exports.js
index da9db9d6c..7cf5b4c0e 100644
--- a/packages-node/providence-analytics/src/program/analyzers/find-exports.js
+++ b/packages-node/providence-analytics/src/program/analyzers/find-exports.js
@@ -1,12 +1,12 @@
/* eslint-disable no-shadow, no-param-reassign */
const pathLib = require('path');
const { default: traverse } = require('@babel/traverse');
-const { Analyzer } = require('./helpers/Analyzer.js');
+const { Analyzer } = require('../core/Analyzer.js');
const { trackDownIdentifier } = require('./helpers/track-down-identifier.js');
const { normalizeSourcePaths } = require('./helpers/normalize-source-paths.js');
const { getReferencedDeclaration } = require('../utils/get-source-code-fragment-of-declaration.js');
-const { LogService } = require('../services/LogService.js');
+const { LogService } = require('../core/LogService.js');
/**
* @typedef {import('./helpers/track-down-identifier.js').RootFile} RootFile
@@ -141,8 +141,8 @@ const isImportingSpecifier = pathOrNode =>
pathOrNode.type === 'ImportDefaultSpecifier' || pathOrNode.type === 'ImportSpecifier';
/**
- * @desc Finds import specifiers and sources for a given ast result
- * @param {BabelAst} ast
+ * Finds import specifiers and sources for a given ast result
+ * @param {File} ast
* @param {FindExportsConfig} config
*/
function findExportsPerAstEntry(ast, { skipFileImports }) {
@@ -207,13 +207,12 @@ function findExportsPerAstEntry(ast, { skipFileImports }) {
}
class FindExportsAnalyzer extends Analyzer {
- constructor() {
- super();
- this.name = 'find-exports';
+ static get analyzerName() {
+ return 'find-exports';
}
/**
- * @desc Finds export specifiers and sources
+ * Finds export specifiers and sources
* @param {FindExportsConfig} customConfig
*/
async execute(customConfig = {}) {
diff --git a/packages-node/providence-analytics/src/program/analyzers/find-imports.js b/packages-node/providence-analytics/src/program/analyzers/find-imports.js
index 9d179efc0..4a8d7b586 100644
--- a/packages-node/providence-analytics/src/program/analyzers/find-imports.js
+++ b/packages-node/providence-analytics/src/program/analyzers/find-imports.js
@@ -2,10 +2,12 @@
const { default: traverse } = require('@babel/traverse');
const { isRelativeSourcePath } = require('../utils/relative-source-path.js');
const { normalizeSourcePaths } = require('./helpers/normalize-source-paths.js');
-const { Analyzer } = require('./helpers/Analyzer.js');
-const { LogService } = require('../services/LogService.js');
+const { Analyzer } = require('../core/Analyzer.js');
+const { LogService } = require('../core/LogService.js');
/**
+ * @typedef {import('@babel/types').File} File
+ * @typedef {import('@babel/types').Node} Node *
* @typedef {import('../types/core').AnalyzerName} AnalyzerName
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult
* @typedef {import('../types/analyzers').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
@@ -16,12 +18,12 @@ const { LogService } = require('../services/LogService.js');
* Options that allow to filter 'on a file basis'.
* We can also filter on the total result
*/
-const /** @type {AnalyzerOptions} */ options = {
+const /** @type {AnalyzerConfig} */ options = {
/**
* Only leaves entries with external sources:
* - keeps: '@open-wc/testing'
* - drops: '../testing'
- * @param {FindImportsAnalyzerResult} result
+ * @param {FindImportsAnalyzerQueryOutput} result
* @param {string} targetSpecifier for instance 'LitElement'
*/
onlyExternalSources(result) {
@@ -29,6 +31,9 @@ const /** @type {AnalyzerOptions} */ options = {
},
};
+/**
+ * @param {Node} node
+ */
function getImportOrReexportsSpecifiers(node) {
return node.specifiers.map(s => {
if (s.type === 'ImportDefaultSpecifier' || s.type === 'ExportDefaultSpecifier') {
@@ -49,11 +54,12 @@ function getImportOrReexportsSpecifiers(node) {
/**
* Finds import specifiers and sources
- * @param {any} ast
+ * @param {File} ast
*/
function findImportsPerAstEntry(ast) {
LogService.debug(`Analyzer "find-imports": started findImportsPerAstEntry method`);
+ // https://github.com/babel/babel/blob/672a58660f0b15691c44582f1f3fdcdac0fa0d2f/packages/babel-core/src/transformation/index.ts#L110
// Visit AST...
/** @type {Partial[]} */
const transformedEntry = [];
@@ -110,10 +116,9 @@ function findImportsPerAstEntry(ast) {
}
class FindImportsAnalyzer extends Analyzer {
- constructor() {
- super();
- /** @type {AnalyzerName} */
- this.name = 'find-imports';
+ /** @type {AnalyzerName} */
+ static get analyzerName() {
+ return 'find-imports';
}
/**
diff --git a/packages-node/providence-analytics/src/program/analyzers/helpers/from-import-to-export-perspective.js b/packages-node/providence-analytics/src/program/analyzers/helpers/from-import-to-export-perspective.js
index 6a7dc8873..e6df1cd8c 100644
--- a/packages-node/providence-analytics/src/program/analyzers/helpers/from-import-to-export-perspective.js
+++ b/packages-node/providence-analytics/src/program/analyzers/helpers/from-import-to-export-perspective.js
@@ -1,29 +1,14 @@
const { isRelativeSourcePath } = require('../../utils/relative-source-path.js');
-const { LogService } = require('../../services/LogService.js');
+const { LogService } = require('../../core/LogService.js');
const { resolveImportPath } = require('../../utils/resolve-import-path.js');
+const { toPosixPath } = require('../../utils/to-posix-path.js');
/**
* @typedef {import('../../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
+ * @typedef {import('../../types/core').PathFromSystemRoot} PathFromSystemRoot
+ * @typedef {import('../../types/core').SpecifierSource} SpecifierSource
*/
-/**
- * @param {string} importee like '@lion/core/myFile.js'
- * @returns {string} project name ('@lion/core')
- */
-function getProjectFromImportee(importee) {
- const scopedProject = importee[0] === '@';
- // 'external-project/src/file.js' -> ['external-project', 'src', file.js']
- let splitSource = importee.split('/');
- if (scopedProject) {
- // '@external/project'
- splitSource = [splitSource.slice(0, 2).join('/'), ...splitSource.slice(2)];
- }
- // ['external-project', 'src', 'file.js'] -> 'external-project'
- const project = splitSource.slice(0, 1).join('/');
-
- return project;
-}
-
/**
* Gets local path from reference project
*
@@ -33,28 +18,25 @@ function getProjectFromImportee(importee) {
* - from: 'reference-project'
* - to: './index.js' (or other file specified in package.json 'main')
* @param {object} config
- * @param {string} config.importee 'reference-project/foo.js'
- * @param {string} config.importer '/my/project/importing-file.js'
+ * @param {SpecifierSource} config.importee 'reference-project/foo.js'
+ * @param {PathFromSystemRoot} config.importer '/my/project/importing-file.js'
+ * @param {PathFromSystemRoot} config.importeeProjectPath '/path/to/reference/project'
* @returns {Promise} './foo.js'
*/
-async function fromImportToExportPerspective({ importee, importer }) {
+async function fromImportToExportPerspective({ importee, importer, importeeProjectPath }) {
if (isRelativeSourcePath(importee)) {
LogService.warn('[fromImportToExportPerspective] Please only provide external import paths');
return null;
}
const absolutePath = await resolveImportPath(importee, importer);
- const projectName = getProjectFromImportee(importee);
+ if (!absolutePath) {
+ return null;
+ }
- /**
- * - from: '/my/reference/project/packages/foo/index.js'
- * - to: './packages/foo/index.js'
- */
- return absolutePath
- ? /** @type {PathRelativeFromProjectRoot} */ (
- absolutePath.replace(new RegExp(`^.*/${projectName}/?(.*)$`), './$1')
- )
- : null;
+ return /** @type {PathRelativeFromProjectRoot} */ (
+ absolutePath.replace(new RegExp(`^${toPosixPath(importeeProjectPath)}/?(.*)$`), './$1')
+ );
}
module.exports = { fromImportToExportPerspective };
diff --git a/packages-node/providence-analytics/src/program/analyzers/helpers/normalize-source-paths.js b/packages-node/providence-analytics/src/program/analyzers/helpers/normalize-source-paths.js
index d2581618c..763788850 100644
--- a/packages-node/providence-analytics/src/program/analyzers/helpers/normalize-source-paths.js
+++ b/packages-node/providence-analytics/src/program/analyzers/helpers/normalize-source-paths.js
@@ -3,7 +3,6 @@ const pathLib = require('path');
const { isRelativeSourcePath } = require('../../utils/relative-source-path.js');
const { resolveImportPath } = require('../../utils/resolve-import-path.js');
const { toPosixPath } = require('../../utils/to-posix-path.js');
-const { aMap } = require('../../utils/async-array-utils.js');
/**
* @typedef {import('../../types/core').PathRelative} PathRelative
@@ -44,7 +43,9 @@ async function normalizeSourcePaths(queryOutput, relativePath, rootPath = proces
pathLib.resolve(rootPath, relativePath)
);
const currentDirPath = /** @type {PathFromSystemRoot} */ (pathLib.dirname(currentFilePath));
- return aMap(queryOutput, async specifierResObj => {
+
+ const normalizedQueryOutput = [];
+ for (const specifierResObj of queryOutput) {
if (specifierResObj.source) {
if (isRelativeSourcePath(specifierResObj.source) && relativePath) {
// This will be a source like '../my/file.js' or './file.js'
@@ -60,8 +61,9 @@ async function normalizeSourcePaths(queryOutput, relativePath, rootPath = proces
// specifierResObj.fullSource = specifierResObj.source;
}
}
- return specifierResObj;
- });
+ normalizedQueryOutput.push(specifierResObj);
+ }
+ return normalizedQueryOutput;
}
module.exports = { normalizeSourcePaths };
diff --git a/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier.js b/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier.js
index 2b5e7aba5..447a3d79d 100644
--- a/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier.js
+++ b/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier.js
@@ -5,10 +5,10 @@ const {
isRelativeSourcePath,
toRelativeSourcePath,
} = require('../../utils/relative-source-path.js');
-const { AstService } = require('../../services/AstService.js');
-const { LogService } = require('../../services/LogService.js');
-const { InputDataService } = require('../../services/InputDataService.js');
+const { InputDataService } = require('../../core/InputDataService.js');
const { resolveImportPath } = require('../../utils/resolve-import-path.js');
+const { AstService } = require('../../core/AstService.js');
+const { LogService } = require('../../core/LogService.js');
const { memoize } = require('../../utils/memoize.js');
/**
@@ -18,6 +18,14 @@ const { memoize } = require('../../utils/memoize.js');
* @typedef {import('../../types/core').PathFromSystemRoot} PathFromSystemRoot
*/
+/**
+ * @param {string} source
+ * @param {string} projectName
+ */
+function isSelfReferencingProject(source, projectName) {
+ return source.startsWith(`${projectName}`);
+}
+
/**
* @param {string} source
* @param {string} projectName
@@ -26,7 +34,7 @@ function isExternalProject(source, projectName) {
return (
!source.startsWith('#') &&
!isRelativeSourcePath(source) &&
- !source.startsWith(`${projectName}/`)
+ !isSelfReferencingProject(source, projectName)
);
}
diff --git a/packages-node/providence-analytics/src/program/analyzers/index.js b/packages-node/providence-analytics/src/program/analyzers/index.js
index 929fba41f..b1656356c 100644
--- a/packages-node/providence-analytics/src/program/analyzers/index.js
+++ b/packages-node/providence-analytics/src/program/analyzers/index.js
@@ -1,5 +1,5 @@
// A base class for writing Analyzers
-const { Analyzer } = require('./helpers/Analyzer.js');
+const { Analyzer } = require('../core/Analyzer.js');
// Expose analyzers that are requested to be run in external contexts
const FindExportsAnalyzer = require('./find-exports.js');
diff --git a/packages-node/providence-analytics/src/program/analyzers/match-imports.js b/packages-node/providence-analytics/src/program/analyzers/match-imports.js
index 1f83ecb8f..adcc8c6cb 100644
--- a/packages-node/providence-analytics/src/program/analyzers/match-imports.js
+++ b/packages-node/providence-analytics/src/program/analyzers/match-imports.js
@@ -3,7 +3,7 @@ const pathLib = require('path');
/* eslint-disable no-shadow, no-param-reassign */
const FindImportsAnalyzer = require('./find-imports.js');
const FindExportsAnalyzer = require('./find-exports.js');
-const { Analyzer } = require('./helpers/Analyzer.js');
+const { Analyzer } = require('../core/Analyzer.js');
const { fromImportToExportPerspective } = require('./helpers/from-import-to-export-perspective.js');
const {
transformIntoIterableFindExportsOutput,
@@ -21,6 +21,7 @@ const {
* @typedef {import('../types/analyzers').MatchImportsConfig} MatchImportsConfig
* @typedef {import('../types/analyzers').MatchImportsAnalyzerResult} MatchImportsAnalyzerResult
* @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
+ * @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../types/core').AnalyzerName} AnalyzerName
*/
@@ -117,9 +118,13 @@ async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerRes
*/
const fromImportToExport = await fromImportToExportPerspective({
importee: importEntry.normalizedSource,
- importer: pathLib.resolve(importProjectPath, importEntry.file),
+ importer: /** @type {PathFromSystemRoot} */ (
+ pathLib.resolve(importProjectPath, importEntry.file)
+ ),
+ importeeProjectPath: cfg.referenceProjectPath,
});
const isFromSameSource = compareImportAndExportPaths(exportEntry.file, fromImportToExport);
+
if (!isFromSameSource) {
continue;
}
@@ -133,7 +138,10 @@ async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerRes
entry => entry.exportSpecifier && entry.exportSpecifier.id === id,
);
if (resultForCurrentExport) {
- resultForCurrentExport.importProjectFiles.push(importEntry.file);
+ // Prevent that we count double import like "import * as all from 'x'" and "import {smth} from 'x'"
+ if (!resultForCurrentExport.importProjectFiles.includes(importEntry.file)) {
+ resultForCurrentExport.importProjectFiles.push(importEntry.file);
+ }
} else {
conciseResultsArray.push({
exportSpecifier: { id, ...(exportEntry.meta ? { meta: exportEntry.meta } : {}) },
@@ -151,10 +159,8 @@ async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerRes
}
class MatchImportsAnalyzer extends Analyzer {
- constructor() {
- super();
- /** @type {AnalyzerName} */
- this.name = 'match-imports';
+ static get analyzerName() {
+ return 'match-imports';
}
static get requiresReference() {
@@ -207,6 +213,7 @@ class MatchImportsAnalyzer extends Analyzer {
metaConfig: cfg.metaConfig,
targetProjectPath: cfg.referenceProjectPath,
skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility,
+ suppressNonCriticalLogs: true,
});
}
@@ -217,6 +224,7 @@ class MatchImportsAnalyzer extends Analyzer {
metaConfig: cfg.metaConfig,
targetProjectPath: cfg.targetProjectPath,
skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility,
+ suppressNonCriticalLogs: true,
});
}
diff --git a/packages-node/providence-analytics/src/program/analyzers/match-paths.js b/packages-node/providence-analytics/src/program/analyzers/match-paths.js
index 8bc30047c..64b1f418a 100644
--- a/packages-node/providence-analytics/src/program/analyzers/match-paths.js
+++ b/packages-node/providence-analytics/src/program/analyzers/match-paths.js
@@ -2,14 +2,14 @@
const MatchSubclassesAnalyzer = require('./match-subclasses.js');
const FindExportsAnalyzer = require('./find-exports.js');
const FindCustomelementsAnalyzer = require('./find-customelements.js');
-const { Analyzer } = require('./helpers/Analyzer.js');
+const { Analyzer } = require('../core/Analyzer.js');
-/** @typedef {import('./types').FindExportsAnalyzerResult} FindExportsAnalyzerResult */
-/** @typedef {import('./types').FindCustomelementsAnalyzerResult} FindCustomelementsAnalyzerResult */
-/** @typedef {import('./types').MatchSubclassesAnalyzerResult} MatchSubclassesAnalyzerResult */
-/** @typedef {import('./types').FindImportsAnalyzerResult} FindImportsAnalyzerResult */
-/** @typedef {import('./types').MatchedExportSpecifier} MatchedExportSpecifier */
-/** @typedef {import('./types').RootFile} RootFile */
+/** @typedef {import('../types/core').FindExportsAnalyzerResult} FindExportsAnalyzerResult */
+/** @typedef {import('../types/core').FindCustomelementsAnalyzerResult} FindCustomelementsAnalyzerResult */
+/** @typedef {import('../types/core').MatchSubclassesAnalyzerResult} MatchSubclassesAnalyzerResult */
+/** @typedef {import('../types/core').FindImportsAnalyzerResult} FindImportsAnalyzerResult */
+/** @typedef {import('../types/core').MatchedExportSpecifier} MatchedExportSpecifier */
+/** @typedef {import('../types/core').RootFile} RootFile */
/**
* For prefix `{ from: 'lion', to: 'wolf' }`
@@ -362,9 +362,8 @@ function matchPathsPostprocess(
* ]
*/
class MatchPathsAnalyzer extends Analyzer {
- constructor() {
- super();
- this.name = 'match-paths';
+ static get analyzerName() {
+ return 'match-paths';
}
static get requiresReference() {
@@ -429,6 +428,7 @@ class MatchPathsAnalyzer extends Analyzer {
gatherFilesConfig: cfg.gatherFilesConfig,
gatherFilesConfigReference: cfg.gatherFilesConfigReference,
skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility,
+ suppressNonCriticalLogs: true,
});
// [A2]
@@ -438,6 +438,7 @@ class MatchPathsAnalyzer extends Analyzer {
targetProjectPath: cfg.targetProjectPath,
gatherFilesConfig: cfg.gatherFilesConfig,
skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility,
+ suppressNonCriticalLogs: true,
});
// [A3]
@@ -447,6 +448,7 @@ class MatchPathsAnalyzer extends Analyzer {
targetProjectPath: cfg.referenceProjectPath,
gatherFilesConfig: cfg.gatherFilesConfigReference,
skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility,
+ suppressNonCriticalLogs: true,
});
/**
@@ -475,6 +477,7 @@ class MatchPathsAnalyzer extends Analyzer {
targetProjectPath: cfg.targetProjectPath,
gatherFilesConfig: cfg.gatherFilesConfig,
skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility,
+ suppressNonCriticalLogs: true,
});
// [B2]
@@ -484,6 +487,7 @@ class MatchPathsAnalyzer extends Analyzer {
targetProjectPath: cfg.referenceProjectPath,
gatherFilesConfig: cfg.gatherFilesConfigReference,
skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility,
+ suppressNonCriticalLogs: true,
});
// refFindExportsAnalyzer was already created in A3
diff --git a/packages-node/providence-analytics/src/program/analyzers/match-subclasses.js b/packages-node/providence-analytics/src/program/analyzers/match-subclasses.js
index 02d6965d2..92b03e052 100644
--- a/packages-node/providence-analytics/src/program/analyzers/match-subclasses.js
+++ b/packages-node/providence-analytics/src/program/analyzers/match-subclasses.js
@@ -3,18 +3,19 @@ const pathLib = require('path');
/* eslint-disable no-shadow, no-param-reassign */
const FindClassesAnalyzer = require('./find-classes.js');
const FindExportsAnalyzer = require('./find-exports.js');
-const { Analyzer } = require('./helpers/Analyzer.js');
+const { Analyzer } = require('../core/Analyzer.js');
const { fromImportToExportPerspective } = require('./helpers/from-import-to-export-perspective.js');
/**
- * @typedef {import('../types/analyzers/find-classes').FindClassesAnalyzerResult} FindClassesAnalyzerResult
- * @typedef {import('../types/find-imports').FindImportsAnalyzerResult} FindImportsAnalyzerResult
- * @typedef {import('../types/find-exports').FindExportsAnalyzerResult} FindExportsAnalyzerResult
- * @typedef {import('../types/find-exports').IterableFindExportsAnalyzerEntry} IterableFindExportsAnalyzerEntry
- * @typedef {import('../types/find-imports').IterableFindImportsAnalyzerEntry} IterableFindImportsAnalyzerEntry
- * @typedef {import('../types/match-imports').ConciseMatchImportsAnalyzerResult} ConciseMatchImportsAnalyzerResult
- * @typedef {import('../types/match-imports').MatchImportsConfig} MatchImportsConfig
- * @typedef {import('../types/core/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
+ * @typedef {import('../types/analyzers').FindClassesAnalyzerResult} FindClassesAnalyzerResult
+ * @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult
+ * @typedef {import('../types/analyzers').FindExportsAnalyzerResult} FindExportsAnalyzerResult
+ * @typedef {import('../types/analyzers').IterableFindExportsAnalyzerEntry} IterableFindExportsAnalyzerEntry
+ * @typedef {import('../types/analyzers').IterableFindImportsAnalyzerEntry} IterableFindImportsAnalyzerEntry
+ * @typedef {import('../types/analyzers').ConciseMatchImportsAnalyzerResult} ConciseMatchImportsAnalyzerResult
+ * @typedef {import('../types/analyzers').MatchImportsConfig} MatchImportsConfig
+ * @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
+ * @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot
*/
function getMemberOverrides(
@@ -52,7 +53,7 @@ function getMemberOverrides(
}
/**
- * @desc Helper method for matchImportsPostprocess. Modifies its resultsObj
+ * Helper method for matchImportsPostprocess. Modifies its resultsObj
* @param {object} resultsObj
* @param {string} exportId like 'myExport::./reference-project/my/export.js::my-project'
* @param {Set} filteredList
@@ -67,14 +68,14 @@ function storeResult(resultsObj, exportId, filteredList, meta) {
}
/**
- * @param {FindExportsAnalyzerResult} exportsAnalyzerResult
+ * @param {FindExportsAnalyzerResult} refExportsAnalyzerResult
* @param {FindClassesAnalyzerResult} targetClassesAnalyzerResult
* @param {FindClassesAnalyzerResult} refClassesAResult
* @param {MatchSubclassesConfig} customConfig
* @returns {AnalyzerQueryResult}
*/
async function matchSubclassesPostprocess(
- exportsAnalyzerResult,
+ refExportsAnalyzerResult,
targetClassesAnalyzerResult,
refClassesAResult,
customConfig,
@@ -102,8 +103,8 @@ async function matchSubclassesPostprocess(
*/
const resultsObj = {};
- for (const exportEntry of exportsAnalyzerResult.queryOutput) {
- const exportsProjectObj = exportsAnalyzerResult.analyzerMeta.targetProject;
+ for (const exportEntry of refExportsAnalyzerResult.queryOutput) {
+ const exportsProjectObj = refExportsAnalyzerResult.analyzerMeta.targetProject;
const exportsProjectName = exportsProjectObj.name;
// Look for all specifiers that are exported, like [import {specifier} 'lion-based-ui/foo.js']
@@ -124,9 +125,10 @@ async function matchSubclassesPostprocess(
// TODO: What if this info is retrieved from cached importProject/target project?
const importProjectPath = cfg.targetProjectPath;
for (const { result, file } of targetClassesAnalyzerResult.queryOutput) {
- // targetClassesAnalyzerResult.queryOutput.forEach(({ result, file }) =>
+ const importerFilePath = /** @type {PathFromSystemRoot} */ (
+ pathLib.resolve(importProjectPath, file)
+ );
for (const classEntryResult of result) {
- // result.forEach(classEntryResult => {
/**
* @example
* Example context (read by 'find-classes'/'find-exports' analyzers)
@@ -165,7 +167,8 @@ async function matchSubclassesPostprocess(
exportEntry.file ===
(await fromImportToExportPerspective({
importee: classMatch.rootFile.file,
- importer: pathLib.resolve(importProjectPath, file),
+ importer: importerFilePath,
+ importeeProjectPath: cfg.referenceProjectPath,
}));
if (classMatch && isFromSameSource) {
@@ -176,8 +179,14 @@ async function matchSubclassesPostprocess(
exportEntryResult,
exportSpecifier,
);
+
+ let projectFileId = `${importProject}::${file}::${classEntryResult.name}`;
+ if (cfg.addSystemPathsInResult) {
+ projectFileId += `::${importerFilePath}`;
+ }
+
filteredImportsList.add({
- projectFileId: `${importProject}::${file}::${classEntryResult.name}`,
+ projectFileId,
memberOverrides,
});
}
@@ -235,13 +244,18 @@ async function matchSubclassesPostprocess(
const matchesPerProject = [];
flatResult.files.forEach(({ projectFileId, memberOverrides }) => {
// eslint-disable-next-line no-shadow
- const [project, file, identifier] = projectFileId.split('::');
+ const [project, file, identifier, filePath] = projectFileId.split('::');
let projectEntry = matchesPerProject.find(m => m.project === project);
if (!projectEntry) {
matchesPerProject.push({ project, files: [] });
projectEntry = matchesPerProject[matchesPerProject.length - 1];
}
- projectEntry.files.push({ file, identifier, memberOverrides });
+ const entry = { file, identifier, memberOverrides };
+ if (filePath) {
+ // @ts-ignore
+ entry.filePath = filePath;
+ }
+ projectEntry.files.push(entry);
});
return {
@@ -262,9 +276,8 @@ async function matchSubclassesPostprocess(
// }
class MatchSubclassesAnalyzer extends Analyzer {
- constructor() {
- super();
- this.name = 'match-subclasses';
+ static get analyzerName() {
+ return 'match-subclasses';
}
static get requiresReference() {
@@ -309,16 +322,18 @@ class MatchSubclassesAnalyzer extends Analyzer {
*/
const findExportsAnalyzer = new FindExportsAnalyzer();
/** @type {FindExportsAnalyzerResult} */
- const exportsAnalyzerResult = await findExportsAnalyzer.execute({
+ const refExportsAnalyzerResult = await findExportsAnalyzer.execute({
targetProjectPath: cfg.referenceProjectPath,
gatherFilesConfig: cfg.gatherFilesConfigReference,
skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility,
+ suppressNonCriticalLogs: true,
});
const findClassesAnalyzer = new FindClassesAnalyzer();
/** @type {FindClassesAnalyzerResult} */
const targetClassesAnalyzerResult = await findClassesAnalyzer.execute({
targetProjectPath: cfg.targetProjectPath,
skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility,
+ suppressNonCriticalLogs: true,
});
const findRefClassesAnalyzer = new FindClassesAnalyzer();
/** @type {FindClassesAnalyzerResult} */
@@ -326,10 +341,11 @@ class MatchSubclassesAnalyzer extends Analyzer {
targetProjectPath: cfg.referenceProjectPath,
gatherFilesConfig: cfg.gatherFilesConfigReference,
skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility,
+ suppressNonCriticalLogs: true,
});
const queryOutput = await matchSubclassesPostprocess(
- exportsAnalyzerResult,
+ refExportsAnalyzerResult,
targetClassesAnalyzerResult,
refClassesAnalyzerResult,
cfg,
diff --git a/packages-node/providence-analytics/src/program/analyzers/post-processors/sort-by-specifier.js b/packages-node/providence-analytics/src/program/analyzers/post-processors/sort-by-specifier.js
index 0339d5605..b03123c22 100644
--- a/packages-node/providence-analytics/src/program/analyzers/post-processors/sort-by-specifier.js
+++ b/packages-node/providence-analytics/src/program/analyzers/post-processors/sort-by-specifier.js
@@ -1,5 +1,5 @@
const pathLib = require('path');
-const { LogService } = require('../../services/LogService.js');
+const { LogService } = require('../../core/LogService.js');
const /** @type {AnalyzerOptions} */ options = {
filterSpecifier(results, targetSpecifier, specifiersKey) {
diff --git a/packages-node/providence-analytics/src/program/analyzers/helpers/Analyzer.js b/packages-node/providence-analytics/src/program/core/Analyzer.js
similarity index 69%
rename from packages-node/providence-analytics/src/program/analyzers/helpers/Analyzer.js
rename to packages-node/providence-analytics/src/program/core/Analyzer.js
index f723bcc11..e7191ca8c 100644
--- a/packages-node/providence-analytics/src/program/analyzers/helpers/Analyzer.js
+++ b/packages-node/providence-analytics/src/program/core/Analyzer.js
@@ -1,25 +1,22 @@
/* eslint-disable no-param-reassign */
-const fs = require('fs');
const semver = require('semver');
const pathLib = require('path');
-const { LogService } = require('../../services/LogService.js');
-const { QueryService } = require('../../services/QueryService.js');
-const { ReportService } = require('../../services/ReportService.js');
-const { InputDataService } = require('../../services/InputDataService.js');
-const { toPosixPath } = require('../../utils/to-posix-path.js');
-const { getFilePathRelativeFromRoot } = require('../../utils/get-file-path-relative-from-root.js');
+const { LogService } = require('./LogService.js');
+const { QueryService } = require('./QueryService.js');
+const { ReportService } = require('./ReportService.js');
+const { InputDataService } = require('./InputDataService.js');
+const { toPosixPath } = require('../utils/to-posix-path.js');
+const { memoize } = require('../utils/memoize.js');
+const { getFilePathRelativeFromRoot } = require('../utils/get-file-path-relative-from-root.js');
/**
- * @typedef {import('../../types/core').AnalyzerName} AnalyzerName
- * @typedef {import('../../types/core').PathFromSystemRoot} PathFromSystemRoot
- * @typedef {import('../../types/core').QueryOutput} QueryOutput
- * @typedef {import('../../types/core').QueryOutputEntry} QueryOutputEntry
- * @typedef {import('../../types/core').ProjectInputData} ProjectInputData
- * @typedef {import('../../types/core').ProjectInputDataWithMeta} ProjectInputDataWithMeta
- * @typedef {import('../../types/core').AnalyzerQueryResult} AnalyzerQueryResult
- * @typedef {import('../../types/core').MatchAnalyzerConfig} MatchAnalyzerConfig
- *
- * @typedef {(ast: object, { relativePath: PathRelative }) => {result: QueryOutputEntry}} TraversEntryFn
+ * @typedef {import('../types/core').AnalyzerName} AnalyzerName
+ * @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot
+ * @typedef {import('../types/core').QueryOutput} QueryOutput
+ * @typedef {import('../types/core').ProjectInputData} ProjectInputData
+ * @typedef {import('../types/core').ProjectInputDataWithMeta} ProjectInputDataWithMeta
+ * @typedef {import('../types/core').AnalyzerQueryResult} AnalyzerQueryResult
+ * @typedef {import('../types/core').MatchAnalyzerConfig} MatchAnalyzerConfig
*/
/**
@@ -27,7 +24,7 @@ const { getFilePathRelativeFromRoot } = require('../../utils/get-file-path-relat
* @param {ProjectInputDataWithMeta} projectData
* @param {function} astAnalysis
*/
-async function analyzePerAstEntry(projectData, astAnalysis) {
+async function analyzePerAstFile(projectData, astAnalysis) {
const entries = [];
for (const { file, ast, context: astContext } of projectData.entries) {
const relativePath = getFilePathRelativeFromRoot(file, projectData.project.path);
@@ -64,22 +61,22 @@ function posixify(data) {
}
/**
- * @desc This method ensures that the result returned by an analyzer always has a consistent format.
+ * 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
* under the same circumstances
* @param {QueryOutput} queryOutput
- * @param {object} configuration
+ * @param {object} cfg
* @param {Analyzer} analyzer
*/
-function ensureAnalyzerResultFormat(queryOutput, configuration, analyzer) {
+function ensureAnalyzerResultFormat(queryOutput, cfg, analyzer) {
const { targetProjectMeta, identifier, referenceProjectMeta } = analyzer;
const optional = {};
if (targetProjectMeta) {
- optional.targetProject = targetProjectMeta;
+ optional.targetProject = { ...targetProjectMeta };
delete optional.targetProject.path; // get rid of machine specific info
}
if (referenceProjectMeta) {
- optional.referenceProject = referenceProjectMeta;
+ optional.referenceProject = { ...referenceProjectMeta };
delete optional.referenceProject.path; // get rid of machine specific info
}
@@ -91,7 +88,7 @@ function ensureAnalyzerResultFormat(queryOutput, configuration, analyzer) {
requiredAst: analyzer.requiredAst,
identifier,
...optional,
- configuration,
+ configuration: cfg,
},
};
@@ -129,28 +126,33 @@ function ensureAnalyzerResultFormat(queryOutput, configuration, analyzer) {
* Before running the analyzer, we need two conditions for a 'compatible match':
* - 1. referenceProject is imported by targetProject at all
* - 2. referenceProject and targetProject have compatible major versions
- * @param {PathFromSystemRoot} referencePath
- * @param {PathFromSystemRoot} targetPath
+ * @typedef {(referencePath:PathFromSystemRoot,targetPath:PathFromSystemRoot) => {compatible:boolean}} CheckForMatchCompatibilityFn
+ * @type {CheckForMatchCompatibilityFn}
*/
-function checkForMatchCompatibility(referencePath, targetPath) {
- const refFile = pathLib.resolve(referencePath, 'package.json');
- const referencePkg = JSON.parse(fs.readFileSync(refFile, 'utf8'));
- const targetFile = pathLib.resolve(targetPath, 'package.json');
- const targetPkg = JSON.parse(fs.readFileSync(targetFile, 'utf8'));
+const checkForMatchCompatibility = memoize(
+ (
+ /** @type {PathFromSystemRoot} */ referencePath,
+ /** @type {PathFromSystemRoot} */ targetPath,
+ ) => {
+ // const refFile = pathLib.resolve(referencePath, 'package.json');
+ const referencePkg = InputDataService.getPackageJson(referencePath);
+ // const targetFile = pathLib.resolve(targetPath, 'package.json');
+ const targetPkg = InputDataService.getPackageJson(targetPath);
- const allTargetDeps = [
- ...Object.entries(targetPkg.devDependencies || {}),
- ...Object.entries(targetPkg.dependencies || {}),
- ];
- const importEntry = allTargetDeps.find(([name]) => referencePkg.name === name);
- if (!importEntry) {
- return { compatible: false, reason: 'no-dependency' };
- }
- if (!semver.satisfies(referencePkg.version, importEntry[1])) {
- return { compatible: false, reason: 'no-matched-version' };
- }
- return { compatible: true };
-}
+ const allTargetDeps = [
+ ...Object.entries(targetPkg.devDependencies || {}),
+ ...Object.entries(targetPkg.dependencies || {}),
+ ];
+ const importEntry = allTargetDeps.find(([name]) => referencePkg.name === name);
+ if (!importEntry) {
+ return { compatible: false, reason: 'no-dependency' };
+ }
+ if (!semver.satisfies(referencePkg.version, importEntry[1])) {
+ return { compatible: false, reason: 'no-matched-version' };
+ }
+ return { compatible: true };
+ },
+);
/**
* If in json format, 'unwind' to be compatible for analysis...
@@ -163,19 +165,18 @@ function unwindJsonResult(targetOrReferenceProjectResult) {
}
class Analyzer {
- constructor() {
- this.requiredAst = 'babel';
- /** @type {AnalyzerName|''} */
- this.name = '';
- }
+ static requiresReference = false;
- static get requiresReference() {
- return false;
- }
+ /** @type {AnalyzerName|''} */
+ static analyzerName = '';
+
+ name = /** @type {typeof Analyzer} */ (this.constructor).analyzerName;
+
+ requiredAst = 'babel';
/**
- * In a MatchAnalyzer, two Analyzers (a reference and target) are run.
- * For instance: a FindExportsAnalyzer and FindImportsAnalyzer are run.
+ * 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.
* When they were stored in json format in the filesystem, 'unwind' them to be compatible for analysis...
* @param {MatchAnalyzerConfig} cfg
@@ -198,13 +199,13 @@ class Analyzer {
this.constructor.__unwindProvidedResults(cfg);
if (!cfg.targetProjectResult) {
- this.targetProjectMeta = InputDataService.getProjectMeta(cfg.targetProjectPath, true);
+ this.targetProjectMeta = InputDataService.getProjectMeta(cfg.targetProjectPath);
} 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);
} else if (cfg.referenceProjectResult) {
this.referenceProjectMeta = cfg.referenceProjectResult.analyzerMeta.targetProject;
}
@@ -227,14 +228,16 @@ class Analyzer {
);
if (!compatible) {
- LogService.info(
- `skipping ${LogService.pad(this.name, 16)} for ${
- this.identifier
- }: (${reason})\n${cfg.targetProjectPath.replace(
- `${process.cwd()}/providence-input-data/search-targets/`,
- '',
- )}`,
- );
+ if (!cfg.suppressNonCriticalLogs) {
+ LogService.info(
+ `skipping ${LogService.pad(this.name, 16)} for ${
+ this.identifier
+ }: (${reason})\n${cfg.targetProjectPath.replace(
+ `${process.cwd()}/providence-input-data/search-targets/`,
+ '',
+ )}`,
+ );
+ }
return ensureAnalyzerResultFormat(`[${reason}]`, cfg, this);
}
}
@@ -245,13 +248,16 @@ class Analyzer {
const cachedResult = Analyzer._getCachedAnalyzerResult({
analyzerName: this.name,
identifier: this.identifier,
+ cfg,
});
if (cachedResult) {
return cachedResult;
}
- LogService.info(`starting ${LogService.pad(this.name, 16)} for ${this.identifier}`);
+ if (!cfg.suppressNonCriticalLogs) {
+ LogService.info(`starting ${LogService.pad(this.name, 16)} for ${this.identifier}`);
+ }
/**
* Get reference and search-target data
@@ -282,12 +288,14 @@ class Analyzer {
LogService.debug(`Analyzer "${this.name}": started _finalize method`);
const analyzerResult = ensureAnalyzerResultFormat(queryOutput, cfg, this);
- LogService.success(`finished ${LogService.pad(this.name, 16)} for ${this.identifier}`);
+ if (!cfg.suppressNonCriticalLogs) {
+ LogService.success(`finished ${LogService.pad(this.name, 16)} for ${this.identifier}`);
+ }
return analyzerResult;
}
/**
- * @param {function|{traverseEntryFn: function: filePaths:string[]; projectPath: string}} traverseEntryOrConfig
+ * @param {function|{traverseEntryFn: function; filePaths:string[]; projectPath: string}} traverseEntryOrConfig
*/
async _traverse(traverseEntryOrConfig) {
LogService.debug(`Analyzer "${this.name}": started _traverse method`);
@@ -323,7 +331,7 @@ class Analyzer {
* Create ASTs for our inputData
*/
const astDataProjects = await QueryService.addAstToProjectsData(finalTargetData, 'babel');
- return analyzePerAstEntry(astDataProjects[0], traverseEntryFn);
+ return analyzePerAstFile(astDataProjects[0], traverseEntryFn);
}
async execute(customConfig = {}) {
@@ -332,6 +340,7 @@ class Analyzer {
const cfg = {
targetProjectPath: null,
referenceProjectPath: null,
+ suppressNonCriticalLogs: false,
...customConfig,
};
@@ -355,19 +364,19 @@ class Analyzer {
}
/**
- * @desc Gets a cached result from ReportService. Since ReportService slightly modifies analyzer
+ * 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
+ * @param {{ analyzerName:AnalyzerName, identifier:string, cfg:AnalyzerConfig}} config
* @returns {AnalyzerQueryResult|undefined}
*/
- static _getCachedAnalyzerResult({ analyzerName, identifier }) {
+ static _getCachedAnalyzerResult({ analyzerName, identifier, cfg }) {
const cachedResult = ReportService.getCachedResult({ analyzerName, identifier });
if (!cachedResult) {
return undefined;
}
- LogService.success(`cached version found for ${identifier}`);
+ if (!cfg.suppressNonCriticalLogs) {
+ LogService.success(`cached version found for ${identifier}`);
+ }
/** @type {AnalyzerQueryResult} */
const result = unwindJsonResult(cachedResult);
diff --git a/packages-node/providence-analytics/src/program/core/AstService.js b/packages-node/providence-analytics/src/program/core/AstService.js
new file mode 100644
index 000000000..214a6f542
--- /dev/null
+++ b/packages-node/providence-analytics/src/program/core/AstService.js
@@ -0,0 +1,74 @@
+const babelParser = require('@babel/parser');
+const parse5 = require('parse5');
+const traverseHtml = require('../utils/traverse-html.js');
+const { LogService } = require('./LogService.js');
+
+/**
+ * @typedef {import("@babel/types").File} File
+ * @typedef {import("@babel/parser").ParserOptions} ParserOptions
+ * @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot
+ */
+
+class AstService {
+ /**
+ * Compiles an array of file paths using Babel.
+ * @param {string} code
+ * @param {ParserOptions} parserOptions
+ * @returns {File}
+ */
+ static _getBabelAst(code, parserOptions = {}) {
+ const ast = babelParser.parse(code, {
+ sourceType: 'module',
+ plugins: [
+ 'importMeta',
+ 'dynamicImport',
+ 'classProperties',
+ 'exportDefaultFrom',
+ 'importAssertions',
+ ],
+ ...parserOptions,
+ });
+ return ast;
+ }
+
+ /**
+ * Combines all script tags as if it were one js file.
+ * @param {string} htmlCode
+ */
+ static getScriptsFromHtml(htmlCode) {
+ const ast = parse5.parseFragment(htmlCode);
+ /**
+ * @type {string[]}
+ */
+ const scripts = [];
+ traverseHtml(ast, {
+ /**
+ * @param {{ node: { childNodes: { value: any; }[]; }; }} path
+ */
+ script(path) {
+ const code = path.node.childNodes[0] ? path.node.childNodes[0].value : '';
+ scripts.push(code);
+ },
+ });
+ return scripts;
+ }
+
+ /**
+ * Returns the Babel AST
+ * @param { string } code
+ * @param { 'babel' } astType
+ * @param { {filePath?: PathFromSystemRoot} } options
+ * @returns {File|undefined}
+ */
+ // eslint-disable-next-line consistent-return
+ static getAst(code, astType, { filePath } = {}) {
+ // eslint-disable-next-line default-case
+ try {
+ return this._getBabelAst(code);
+ } catch (e) {
+ LogService.error(`Error when parsing "${filePath}":/n${e}`);
+ }
+ }
+}
+
+module.exports = { AstService };
diff --git a/packages-node/providence-analytics/src/program/services/InputDataService.js b/packages-node/providence-analytics/src/program/core/InputDataService.js
similarity index 83%
rename from packages-node/providence-analytics/src/program/services/InputDataService.js
rename to packages-node/providence-analytics/src/program/core/InputDataService.js
index 4fac82170..e8cdd0778 100644
--- a/packages-node/providence-analytics/src/program/services/InputDataService.js
+++ b/packages-node/providence-analytics/src/program/core/InputDataService.js
@@ -10,11 +10,13 @@ const { LogService } = require('./LogService.js');
const { AstService } = require('./AstService.js');
const { getFilePathRelativeFromRoot } = require('../utils/get-file-path-relative-from-root.js');
const { toPosixPath } = require('../utils/to-posix-path.js');
+const { memoize } = require('../utils/memoize.js');
/**
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult
* @typedef {import('../types/analyzers').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
* @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
+ * @typedef {import('../types/core').PathRelative} PathRelative
* @typedef {import('../types/core').QueryConfig} QueryConfig
* @typedef {import('../types/core').QueryResult} QueryResult
* @typedef {import('../types/core').FeatureQueryConfig} FeatureQueryConfig
@@ -31,20 +33,15 @@ const { toPosixPath } = require('../utils/to-posix-path.js');
* @typedef {import('../types/core').ProjectInputDataWithMeta} ProjectInputDataWithMeta
* @typedef {import('../types/core').Project} Project
* @typedef {import('../types/core').ProjectName} ProjectName
- */
-
-/**
+ * @typedef {import('../types/core').PackageJson} PackageJson
* @typedef {{path:PathFromSystemRoot; name:ProjectName}} ProjectNameAndPath
- * @typedef {{name:ProjectName;files:PathRelativeFromProjectRoot[], workspaces:string[]}} PkgJson
*/
-// TODO: memoize
-
/**
- * @param {PathFromSystemRoot} rootPath
- * @returns {PkgJson|undefined}
+ * @typedef {(rootPath:PathFromSystemRoot) => PackageJson|undefined} GetPackageJsonFn
+ * @type {GetPackageJsonFn}
*/
-function getPackageJson(rootPath) {
+const getPackageJson = memoize((/** @type {PathFromSystemRoot} */ rootPath) => {
try {
const fileContent = fs.readFileSync(`${rootPath}/package.json`, 'utf8');
return JSON.parse(fileContent);
@@ -58,71 +55,75 @@ function getPackageJson(rootPath) {
return undefined;
}
}
-}
+});
/**
- * @param {PathFromSystemRoot} rootPath
+ * @typedef {(rootPath:PathFromSystemRoot) => object|undefined} GetLernaJsonFn
+ * @type {GetLernaJsonFn}
*/
-function getLernaJson(rootPath) {
+const getLernaJson = memoize((/** @type {PathFromSystemRoot} */ rootPath) => {
try {
const fileContent = fs.readFileSync(`${rootPath}/lerna.json`, 'utf8');
return JSON.parse(fileContent);
} catch (_) {
return undefined;
}
-}
+});
/**
- *
- * @param {PathFromSystemRoot[]|string[]} list
- * @param {PathFromSystemRoot} rootPath
- * @returns {ProjectNameAndPath[]}
+ * @typedef {(list:PathFromSystemRoot[]|string[], rootPath:PathFromSystemRoot) => ProjectNameAndPath[]} GetPathsFromGlobListFn
+ * @type {GetPathsFromGlobListFn}
*/
-function getPathsFromGlobList(list, rootPath) {
- /** @type {string[]} */
- const results = [];
- list.forEach(pathOrGlob => {
- if (!pathOrGlob.endsWith('/')) {
- // eslint-disable-next-line no-param-reassign
- pathOrGlob = `${pathOrGlob}/`;
- }
+const getPathsFromGlobList = memoize(
+ (
+ /** @type {PathFromSystemRoot[]|string[]} */ list,
+ /** @type {PathFromSystemRoot} */ rootPath,
+ ) => {
+ /** @type {string[]} */
+ const results = [];
+ list.forEach(pathOrGlob => {
+ if (!pathOrGlob.endsWith('/')) {
+ // eslint-disable-next-line no-param-reassign
+ pathOrGlob = `${pathOrGlob}/`;
+ }
- if (pathOrGlob.includes('*')) {
- const globResults = glob.sync(pathOrGlob, { cwd: rootPath, absolute: false });
- globResults.forEach(r => {
- results.push(r);
- });
- } else {
- results.push(pathOrGlob);
- }
- });
- return results.map(pkgPath => {
- const packageRoot = pathLib.resolve(rootPath, pkgPath);
- const basename = pathLib.basename(pkgPath);
- const pkgJson = getPackageJson(/** @type {PathFromSystemRoot} */ (packageRoot));
- const name = /** @type {ProjectName} */ ((pkgJson && pkgJson.name) || basename);
- return { name, path: /** @type {PathFromSystemRoot} */ (pkgPath) };
- });
-}
+ if (pathOrGlob.includes('*')) {
+ const globResults = glob.sync(pathOrGlob, { cwd: rootPath, absolute: false });
+ globResults.forEach(r => {
+ results.push(r);
+ });
+ } else {
+ results.push(pathOrGlob);
+ }
+ });
+ return results.map(pkgPath => {
+ const packageRoot = pathLib.resolve(rootPath, pkgPath);
+ const basename = pathLib.basename(pkgPath);
+ const pkgJson = getPackageJson(/** @type {PathFromSystemRoot} */ (packageRoot));
+ const name = /** @type {ProjectName} */ ((pkgJson && pkgJson.name) || basename);
+ return { name, path: /** @type {PathFromSystemRoot} */ (pkgPath) };
+ });
+ },
+);
/**
- * @param {PathFromSystemRoot} rootPath
- * @returns {string|undefined}
+ * @typedef {(rootPath:PathFromSystemRoot) => string|undefined} GetGitignoreFileFn
+ * @type {GetGitignoreFileFn}
*/
-function getGitignoreFile(rootPath) {
+const getGitignoreFile = memoize((/** @type {PathFromSystemRoot} */ rootPath) => {
try {
return fs.readFileSync(`${rootPath}/.gitignore`, 'utf8');
} catch (_) {
return undefined;
}
-}
+});
/**
- * @param {PathFromSystemRoot} rootPath
- * @returns {string[]}
+ * @typedef {(rootPath:PathFromSystemRoot) => string[]} GetGitIgnorePathsFn
+ * @type {GetGitIgnorePathsFn}
*/
-function getGitIgnorePaths(rootPath) {
- const fileContent = getGitignoreFile(rootPath);
+const getGitIgnorePaths = memoize((/** @type {PathFromSystemRoot} */ rootPath) => {
+ const fileContent = /** @type {string} */ (getGitignoreFile(rootPath));
if (!fileContent) {
return [];
}
@@ -154,14 +155,14 @@ function getGitIgnorePaths(rootPath) {
return entry;
});
return normalizedEntries;
-}
+});
/**
* Gives back all files and folders that need to be added to npm artifact
- * @param {PathFromSystemRoot} rootPath
- * @returns {string[]}
+ * @typedef {(rootPath:PathFromSystemRoot) => string[]} GetNpmPackagePathsFn
+ * @type {GetNpmPackagePathsFn}
*/
-function getNpmPackagePaths(rootPath) {
+const getNpmPackagePaths = memoize((/** @type {PathFromSystemRoot} */ rootPath) => {
const pkgJson = getPackageJson(rootPath);
if (!pkgJson) {
return [];
@@ -176,7 +177,7 @@ function getNpmPackagePaths(rootPath) {
});
}
return [];
-}
+});
/**
* @param {any|any[]} v
@@ -189,22 +190,31 @@ function ensureArray(v) {
/**
* @param {string|string[]} patterns
* @param {Partial<{keepDirs:boolean;root:string}>} [options]
+ *
+ * @typedef {(patterns:string|string[], opts: {keepDirs?:boolean;root:string}) => string[]} MultiGlobSyncFn
+ * @type {MultiGlobSyncFn}
*/
-function multiGlobSync(patterns, { keepDirs = false, root } = {}) {
- patterns = ensureArray(patterns);
- const res = new Set();
- patterns.forEach(pattern => {
- const files = glob.sync(pattern, { root });
- files.forEach(filePath => {
- if (fs.lstatSync(filePath).isDirectory() && !keepDirs) {
- return;
- }
- res.add(filePath);
+const multiGlobSync = memoize(
+ (/** @type {string|string[]} */ patterns, { keepDirs = false, root } = {}) => {
+ patterns = ensureArray(patterns);
+ const res = new Set();
+ patterns.forEach(pattern => {
+ const files = glob.sync(pattern, { root });
+ files.forEach(filePath => {
+ if (fs.lstatSync(filePath).isDirectory() && !keepDirs) {
+ return;
+ }
+ res.add(filePath);
+ });
});
- });
- return Array.from(res);
-}
+ return Array.from(res);
+ },
+);
+/**
+ * @param {string} localPathWithDotSlash
+ * @returns {string}
+ */
function stripDotSlashFromLocalPath(localPathWithDotSlash) {
return localPathWithDotSlash.replace(/^\.\//, '');
}
@@ -241,9 +251,9 @@ function getStringOrObjectValOfExportMapEntry({ valObjOrStr, nodeResolveMode })
class InputDataService {
/**
* Create an array of ProjectData
- * @param {PathFromSystemRoot | ProjectInputData []} projectPaths
+ * @param {(PathFromSystemRoot|ProjectInputData)[]} projectPaths
* @param {Partial} gatherFilesConfig
- * @returns {ProjectInputData[]}
+ * @returns {ProjectInputDataWithMeta[]}
*/
static createDataObject(projectPaths, gatherFilesConfig = {}) {
/** @type {ProjectInputData[]} */
@@ -306,7 +316,7 @@ class InputDataService {
LogService.warn(/** @type {string} */ (e));
}
project.commitHash = this._getCommitHash(projectPath);
- return /** @type {Project} */ (project);
+ return /** @type {Project} */ (Object.freeze(project));
}
/**
@@ -365,7 +375,7 @@ class InputDataService {
toPosixPath(projectObj.project.path),
);
if (pathLib.extname(file) === '.html') {
- const extractedScripts = AstService.getScriptsFromHtml(code);
+ const extractedScripts = AstService.getScriptsFromHtml(/** @type {string} */ (code));
// eslint-disable-next-line no-shadow
extractedScripts.forEach((code, i) => {
newEntries.push({
@@ -619,7 +629,7 @@ class InputDataService {
}
/**
- * @param {{[key:string]: string|object}} exports
+ * @param {{[key:string]: string|object|null}} exports
* @param {object} opts
* @param {'default'|'development'|string} [opts.nodeResolveMode='default']
* @param {string} opts.packageRootPath
@@ -688,7 +698,14 @@ class InputDataService {
return exportMapPaths;
}
}
-InputDataService.cacheDisabled = false;
+// TODO: Remove memoizeConfig.isCacheDisabled this once whole providence uses cacheConfig instead of
+// memoizeConfig.isCacheDisabled
+// InputDataService.cacheDisabled = memoizeConfig.isCacheDisabled;
+
+InputDataService.getProjectMeta = memoize(InputDataService.getProjectMeta);
+InputDataService.gatherFilesFromDir = memoize(InputDataService.gatherFilesFromDir);
+InputDataService.getMonoRepoPackages = memoize(InputDataService.getMonoRepoPackages);
+InputDataService.createDataObject = memoize(InputDataService.createDataObject);
InputDataService.getPackageJson = getPackageJson;
diff --git a/packages-node/providence-analytics/src/program/services/LogService.js b/packages-node/providence-analytics/src/program/core/LogService.js
similarity index 85%
rename from packages-node/providence-analytics/src/program/services/LogService.js
rename to packages-node/providence-analytics/src/program/core/LogService.js
index 831f65165..992d49950 100644
--- a/packages-node/providence-analytics/src/program/services/LogService.js
+++ b/packages-node/providence-analytics/src/program/core/LogService.js
@@ -1,12 +1,7 @@
const pathLib = require('path');
const chalk = require('chalk');
-const ora = require('ora');
const fs = require('fs');
-/**
- * @typedef {import('ora').Ora} Ora
- */
-
const { log } = console;
/**
@@ -17,9 +12,6 @@ function printTitle(title) {
return `${title ? `${title}\n` : ''}`;
}
-/** @type {Ora} */
-let spinner;
-
class LogService {
/**
* @param {string} text
@@ -89,39 +81,12 @@ class LogService {
static info(text, title) {
// @ts-ignore
this._logHistory.push(`- info -${printTitle(title)} ${text}`);
-
if (this.allMuted) {
return;
}
-
log(chalk.bgBlue.black.bold(` info${printTitle(title)}`), text);
}
- /**
- * @param {string} text
- */
- static spinnerStart(text) {
- spinner = ora(text).start();
- }
-
- /**
- * @param {string} text
- */
- static spinnerText(text) {
- if (!spinner) {
- this.spinnerStart(text);
- }
- spinner.text = text;
- }
-
- static spinnerStop() {
- spinner.stop();
- }
-
- static get spinner() {
- return spinner;
- }
-
/**
* @param {string} text
* @param {number} minChars
diff --git a/packages-node/providence-analytics/src/program/services/QueryService.js b/packages-node/providence-analytics/src/program/core/QueryService.js
similarity index 89%
rename from packages-node/providence-analytics/src/program/services/QueryService.js
rename to packages-node/providence-analytics/src/program/core/QueryService.js
index df45d97f2..0ce9a7ec0 100644
--- a/packages-node/providence-analytics/src/program/services/QueryService.js
+++ b/packages-node/providence-analytics/src/program/core/QueryService.js
@@ -3,6 +3,7 @@ const child_process = require('child_process'); // eslint-disable-line camelcase
const { AstService } = require('./AstService.js');
const { LogService } = require('./LogService.js');
const { getFilePathRelativeFromRoot } = require('../utils/get-file-path-relative-from-root.js');
+const { memoize } = require('../utils/memoize.js');
/**
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult
@@ -31,6 +32,9 @@ class QueryService {
* @returns {SearchQueryConfig}
*/
static getQueryConfigFromRegexSearchString(regexString) {
+ if (typeof regexString !== 'string') {
+ throw new Error('[QueryService.getQueryConfigFromRegexSearchString]: provide a string');
+ }
return { type: 'search', regexString };
}
@@ -44,8 +48,13 @@ class QueryService {
* @returns {FeatureQueryConfig}
*/
static getQueryConfigFromFeatureString(queryString) {
+ if (typeof queryString !== 'string') {
+ throw new Error('[QueryService.getQueryConfigFromFeatureString]: provide a string');
+ }
+
/**
- * @param {string} candidate
+ * Each candidate (tag, attrKey or attrValue) can end with asterisk.
+ * @param {string} candidate for my-*[attr*=x*] 'my-*', 'attr*' or 'x*'
* @returns {[string, boolean]}
*/
function parseContains(candidate) {
@@ -59,12 +68,12 @@ class QueryService {
let featString;
// Creates tag ('tg-icon') and featString ('font-icon+size=xs')
- const match = queryString.match(/(^.*)(\[(.+)\])+/);
- if (match) {
+ const attrMatch = queryString.match(/(^.*)(\[(.+)\])+/);
+ if (attrMatch) {
// eslint-disable-next-line prefer-destructuring
- tagCandidate = match[1];
+ tagCandidate = attrMatch[1];
// eslint-disable-next-line prefer-destructuring
- featString = match[3];
+ featString = attrMatch[3];
} else {
tagCandidate = queryString;
}
@@ -94,9 +103,9 @@ class QueryService {
}
/**
- * RSetrieves the default export found in ./program/analyzers/findImport.js
- * @param {string|Analyzer} analyzerObjectOrString
- * @param {AnalyzerConfig} analyzerConfig
+ * Retrieves the default export found in ./program/analyzers/find-import.js
+ * @param {string|typeof Analyzer} analyzerObjectOrString
+ * @param {AnalyzerConfig} [analyzerConfig]
* @returns {AnalyzerQueryConfig}
*/
static getQueryConfigFromAnalyzer(analyzerObjectOrString, analyzerConfig) {
@@ -108,28 +117,26 @@ class QueryService {
// eslint-disable-next-line import/no-dynamic-require, global-require
analyzer = /** @type {Analyzer} */ (require(`../analyzers/${analyzerObjectOrString}`));
} catch (e) {
- LogService.error(e);
+ LogService.error(e.toString());
process.exit(1);
}
} else {
// We don't need to import the analyzer, since we already have it
analyzer = analyzerObjectOrString;
}
- return {
+ return /** @type {AnalyzerQueryConfig} */ ({
type: 'ast-analyzer',
- analyzerName: /** @type {AnalyzerName} */ (analyzer.name),
+ analyzerName: /** @type {AnalyzerName} */ (analyzer.analyzerName),
analyzerConfig,
analyzer,
- };
+ });
}
/**
- * @desc Search via unix grep
+ * Search via unix grep
* @param {InputData} inputData
- * @param {QueryConfig} queryConfig
- * @param {object} [customConfig]
- * @param {boolean} [customConfig.hasVerboseReporting]
- * @param {object} [customConfig.gatherFilesConfig]
+ * @param {FeatureQueryConfig|SearchQueryConfig} queryConfig
+ * @param {{hasVerboseReporting:boolean;gatherFilesConfig:GatherFilesConfig}} [customConfig]
* @returns {Promise}
*/
static async grepSearch(inputData, queryConfig, customConfig) {
@@ -190,7 +197,7 @@ class QueryService {
}
/**
- * Search via ast (typescript compilation)
+ * Perform ast analysis
* @param {AnalyzerQueryConfig} analyzerQueryConfig
* @param {AnalyzerConfig} [customConfig]
* @returns {Promise}
@@ -341,4 +348,6 @@ class QueryService {
}
QueryService.cacheDisabled = false;
+QueryService.addAstToProjectsData = memoize(QueryService.addAstToProjectsData);
+
module.exports = { QueryService };
diff --git a/packages-node/providence-analytics/src/program/services/ReportService.js b/packages-node/providence-analytics/src/program/core/ReportService.js
similarity index 95%
rename from packages-node/providence-analytics/src/program/services/ReportService.js
rename to packages-node/providence-analytics/src/program/core/ReportService.js
index 2846cd87f..8f311fd76 100644
--- a/packages-node/providence-analytics/src/program/services/ReportService.js
+++ b/packages-node/providence-analytics/src/program/core/ReportService.js
@@ -1,6 +1,8 @@
const fs = require('fs');
const pathLib = require('path');
const getHash = require('../utils/get-hash.js');
+const { memoize } = require('../utils/memoize.js');
+// const memoize = fn => fn;
/**
* @typedef {import('../types/core').Project} Project
@@ -60,6 +62,7 @@ class ReportService {
}
const { name } = queryResult.meta.analyzerMeta;
const filePath = this._getResultFileNameAndPath(name, identifier);
+
fs.writeFileSync(filePath, output, { flag: 'w' });
}
@@ -125,5 +128,7 @@ class ReportService {
fs.writeFileSync(filePath, JSON.stringify(file, null, 2), { flag: 'w' });
}
}
+ReportService.createIdentifier = memoize(ReportService.createIdentifier);
+ReportService.getCachedResult = memoize(ReportService.getCachedResult);
module.exports = { ReportService };
diff --git a/packages-node/providence-analytics/src/program/providence.js b/packages-node/providence-analytics/src/program/providence.js
index e53217262..73254c4b0 100644
--- a/packages-node/providence-analytics/src/program/providence.js
+++ b/packages-node/providence-analytics/src/program/providence.js
@@ -1,12 +1,25 @@
+const { performance } = require('perf_hooks');
const deepmerge = require('deepmerge');
-const { ReportService } = require('./services/ReportService.js');
-const { InputDataService } = require('./services/InputDataService.js');
-const { LogService } = require('./services/LogService.js');
-const { QueryService } = require('./services/QueryService.js');
-const { aForEach } = require('./utils/async-array-utils.js');
+const { ReportService } = require('./core/ReportService.js');
+const { InputDataService } = require('./core/InputDataService.js');
+const { LogService } = require('./core/LogService.js');
+const { QueryService } = require('./core/QueryService.js');
-// After handling a combo, we should know which project versions we have, since
-// the analyzer internally called createDataObject(which provides us the needed meta info).
+/**
+ * @typedef {import('./types/core').ProvidenceConfig} ProvidenceConfig
+ * @typedef {import('./types/core').PathFromSystemRoot} PathFromSystemRoot
+ * @typedef {import('./types/core').QueryResult} QueryResult
+ * @typedef {import('./types/core').AnalyzerQueryResult} AnalyzerQueryResult
+ * @typedef {import('./types/core').QueryConfig} QueryConfig
+ * @typedef {import('./types/core').AnalyzerQueryConfig} AnalyzerQueryConfig
+ * @typedef {import('./types/core').GatherFilesConfig} GatherFilesConfig
+ */
+
+/**
+ * After handling a combo, we should know which project versions we have, since
+ * the analyzer internally called createDataObject(which provides us the needed meta info).
+ * @param {{queryResult: AnalyzerQueryResult; queryConfig: AnalyzerQueryConfig; providenceConfig: ProvidenceConfig}} opts
+ */
function addToSearchTargetDepsFile({ queryResult, queryConfig, providenceConfig }) {
const currentSearchTarget = queryConfig.analyzerConfig.targetProjectPath;
// eslint-disable-next-line array-callback-return, consistent-return
@@ -26,6 +39,10 @@ function addToSearchTargetDepsFile({ queryResult, queryConfig, providenceConfig
});
}
+/**
+ * @param {AnalyzerQueryResult} queryResult
+ * @param {{outputPath:PathFromSystemRoot;report:boolean}} cfg
+ */
function report(queryResult, cfg) {
if (cfg.report && !queryResult.meta.analyzerMeta.__fromCache) {
const { identifier } = queryResult.meta.analyzerMeta;
@@ -35,12 +52,13 @@ function report(queryResult, cfg) {
/**
* Creates unique QueryConfig for analyzer turn
- * @param {QueryConfig} queryConfig
- * @param {string} targetProjectPath
- * @param {string} referenceProjectPath
+ * @param {AnalyzerQueryConfig} queryConfig
+ * @param {PathFromSystemRoot} targetProjectPath
+ * @param {PathFromSystemRoot} referenceProjectPath
+ * @returns {Partial}
*/
function getSlicedQueryConfig(queryConfig, targetProjectPath, referenceProjectPath) {
- return {
+ return /** @type {Partial} */ ({
...queryConfig,
...{
analyzerConfig: {
@@ -51,19 +69,20 @@ function getSlicedQueryConfig(queryConfig, targetProjectPath, referenceProjectPa
},
},
},
- };
+ });
}
/**
- * @desc definition "projectCombo": referenceProject#version + searchTargetProject#version
- * @param {QueryConfig} slicedQConfig
- * @param {cfg} object
+ * Definition "projectCombo": referenceProject#version + searchTargetProject#version
+ * @param {AnalyzerQueryConfig} slicedQConfig
+ * @param {{ gatherFilesConfig:GatherFilesConfig, gatherFilesConfigReference:GatherFilesConfig, skipCheckMatchCompatibility:boolean }} cfg
*/
async function handleAnalyzerForProjectCombo(slicedQConfig, cfg) {
const queryResult = await QueryService.astSearch(slicedQConfig, {
gatherFilesConfig: cfg.gatherFilesConfig,
gatherFilesConfigReference: cfg.gatherFilesConfigReference,
skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility,
+ addSystemPathsInResult: cfg.addSystemPathsInResult,
...slicedQConfig.analyzerConfig,
});
if (queryResult) {
@@ -73,7 +92,7 @@ async function handleAnalyzerForProjectCombo(slicedQConfig, cfg) {
}
/**
- * @desc Here, we will match all our reference projects (exports) against all our search targets
+ * Here, we will match all our reference projects (exports) against all our search targets
* (imports).
*
* This is an expensive operation. Therefore, we allow caching.
@@ -88,16 +107,16 @@ async function handleAnalyzerForProjectCombo(slicedQConfig, cfg) {
* All the json outputs can be aggregated in our dashboard and visually presented in
* various ways.
*
- * @param {QueryConfig} queryConfig
- * @param {ProvidenceConfig} cfg
+ * @param {AnalyzerQueryConfig} queryConfig
+ * @param {Partial} cfg
*/
async function handleAnalyzer(queryConfig, cfg) {
const queryResults = [];
const { referenceProjectPaths, targetProjectPaths } = cfg;
- await aForEach(targetProjectPaths, async searchTargetProject => {
+ for (const searchTargetProject of targetProjectPaths) {
if (referenceProjectPaths) {
- await aForEach(referenceProjectPaths, async ref => {
+ for (const ref of referenceProjectPaths) {
// Create shallow cfg copy with just currrent reference folder
const slicedQueryConfig = getSlicedQueryConfig(queryConfig, searchTargetProject, ref);
const queryResult = await handleAnalyzerForProjectCombo(slicedQueryConfig, cfg);
@@ -109,7 +128,7 @@ async function handleAnalyzer(queryConfig, cfg) {
providenceConfig: cfg,
});
}
- });
+ }
} else {
const slicedQueryConfig = getSlicedQueryConfig(queryConfig, searchTargetProject);
const queryResult = await handleAnalyzerForProjectCombo(slicedQueryConfig, cfg);
@@ -122,7 +141,7 @@ async function handleAnalyzer(queryConfig, cfg) {
});
}
}
- });
+ }
return queryResults;
}
@@ -149,40 +168,37 @@ async function handleRegexSearch(queryConfig, cfg, inputData) {
}
/**
- * @desc Creates a report with usage metrics, based on a queryConfig.
+ * Creates a report with usage metrics, based on a queryConfig.
*
* @param {QueryConfig} queryConfig a query configuration object containing analyzerOptions.
- * @param {object} customConfig
- * @param {'ast'|'grep'} customConfig.queryMethod whether analyzer should be run or a grep should
- * be performed
- * @param {string[]} customConfig.targetProjectPaths search target projects. For instance
- * ['/path/to/app-a', '/path/to/app-b', ... '/path/to/app-z']
- * @param {string[]} [customConfig.referenceProjectPaths] reference projects. Needed for 'match
- * analyzers', having `requiresReference: true`. For instance
- * ['/path/to/lib1', '/path/to/lib2']
- * @param {GatherFilesConfig} [customConfig.gatherFilesConfig]
- * @param {boolean} [customConfig.report]
- * @param {boolean} [customConfig.debugEnabled]
+ * @param {Partial} customConfig
*/
async function providenceMain(queryConfig, customConfig) {
- const cfg = deepmerge(
- {
- queryMethod: 'grep',
- // This is a merge of all 'main entry projects'
- // found in search-targets, including their children
- targetProjectPaths: null,
- referenceProjectPaths: null,
- // This will be needed to identify the parent/child relationship to write to
- // {outputFolder}/entryProjectDependencies.json, which will map
- // a project#version to [ depA#version, depB#version ]
- targetProjectRootPaths: null,
- gatherFilesConfig: {},
- report: true,
- debugEnabled: false,
- writeLogFile: false,
- skipCheckMatchCompatibility: false,
- },
- customConfig,
+ const tStart = performance.now();
+
+ const cfg = /** @type {ProvidenceConfig} */ (
+ deepmerge(
+ {
+ queryMethod: 'grep',
+ // This is a merge of all 'main entry projects'
+ // found in search-targets, including their children
+ targetProjectPaths: null,
+ referenceProjectPaths: null,
+ // This will be needed to identify the parent/child relationship to write to
+ // {outputFolder}/entryProjectDependencies.json, which will map
+ // a project#version to [ depA#version, depB#version ]
+ targetProjectRootPaths: null,
+ gatherFilesConfig: {},
+ report: true,
+ debugEnabled: false,
+ writeLogFile: false,
+ skipCheckMatchCompatibility: false,
+ measurePerformance: false,
+ /** Allows to navigate to source file in code editor */
+ addSystemPathsInResult: false,
+ },
+ customConfig,
+ )
);
if (cfg.debugEnabled) {
@@ -215,6 +231,12 @@ async function providenceMain(queryConfig, customConfig) {
LogService.writeLogFile();
}
+ const tEnd = performance.now();
+
+ if (cfg.measurePerformance) {
+ // eslint-disable-next-line no-console
+ console.log(`completed in ${((tEnd - tStart) / 1000).toFixed(2)} seconds`);
+ }
return queryResults;
}
diff --git a/packages-node/providence-analytics/src/program/services/AstService.js b/packages-node/providence-analytics/src/program/services/AstService.js
deleted file mode 100644
index 1fa5b5c12..000000000
--- a/packages-node/providence-analytics/src/program/services/AstService.js
+++ /dev/null
@@ -1,136 +0,0 @@
-// @ts-nocheck
-const {
- createProgram,
- getPreEmitDiagnostics,
- ModuleKind,
- ModuleResolutionKind,
- ScriptTarget,
-} = require('typescript');
-const babelParser = require('@babel/parser');
-// @ts-expect-error
-const esModuleLexer = require('es-module-lexer');
-const parse5 = require('parse5');
-const traverseHtml = require('../utils/traverse-html.js');
-const { LogService } = require('./LogService.js');
-
-/**
- * @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot
- */
-
-class AstService {
- /**
- * @deprecated for simplicity/maintainability, only allow Babel for js
- * Compiles an array of file paths using Typescript.
- * @param {string[]} filePaths
- * @param {CompilerOptions} options
- */
- static _getTypescriptAst(filePaths, options) {
- // eslint-disable-next-line no-param-reassign
- filePaths = Array.isArray(filePaths) ? filePaths : [filePaths];
-
- const defaultOptions = {
- noEmitOnError: false,
- allowJs: true,
- experimentalDecorators: true,
- target: ScriptTarget.Latest,
- downlevelIteration: true,
- module: ModuleKind.ESNext,
- // module: ModuleKind.CommonJS,
- // lib: ["esnext", "dom"],
- strictNullChecks: true,
- moduleResolution: ModuleResolutionKind.NodeJs,
- esModuleInterop: true,
- noEmit: true,
- allowSyntheticDefaultImports: true,
- allowUnreachableCode: true,
- allowUnusedLabels: true,
- skipLibCheck: true,
- isolatedModules: true,
- };
-
- const program = createProgram(filePaths, options || defaultOptions);
- const diagnostics = getPreEmitDiagnostics(program);
- const files = program.getSourceFiles().filter(sf => filePaths.includes(sf.fileName));
- return { diagnostics, program, files };
- }
-
- /**
- * Compiles an array of file paths using Babel.
- * @param {string} code
- */
- static _getBabelAst(code) {
- const ast = babelParser.parse(code, {
- sourceType: 'module',
- plugins: [
- 'importMeta',
- 'dynamicImport',
- 'classProperties',
- 'exportDefaultFrom',
- 'importAssertions',
- ],
- });
- return ast;
- }
-
- /**
- * Combines all script tags as if it were one js file.
- * @param {string} htmlCode
- */
- static getScriptsFromHtml(htmlCode) {
- const ast = parse5.parseFragment(htmlCode);
- const scripts = [];
- traverseHtml(ast, {
- script(path) {
- const code = path.node.childNodes[0] ? path.node.childNodes[0].value : '';
- scripts.push(code);
- },
- });
- return scripts;
- }
-
- /**
- * @deprecated for simplicity/maintainability, only allow Babel for js
- * @param {string} code
- */
- static async _getEsModuleLexerOutput(code) {
- return esModuleLexer.parse(code);
- }
-
- /**
- * Returns the desired AST
- * Why would we support multiple ASTs/parsers?
- * - 'babel' is our default tool for analysis. It's the most versatile and popular tool, it's
- * close to the EStree standard (other than Typescript) and a lot of plugins and resources can
- * be found online. It also allows to parse Typescript and spec proposals.
- * - 'typescript' (deprecated) is needed for some valuable third party tooling, like web-component-analyzer
- * - 'es-module-lexer' (deprecated) is needed for the dedicated task of finding module imports; it is way
- * quicker than a full fledged AST parser
- * @param { 'babel' } astType
- * @param { {filePath: PathFromSystemRoot} } [options]
- */
- // eslint-disable-next-line consistent-return
- static getAst(code, astType, { filePath } = {}) {
- // eslint-disable-next-line default-case
- try {
- // eslint-disable-next-line default-case
- switch (astType) {
- case 'babel':
- return this._getBabelAst(code);
- case 'typescript':
- LogService.warn(`
- Please notice "typescript" support is deprecated.
- For parsing javascript, "babel" is recommended.`);
- return this._getTypescriptAst(code);
- case 'es-module-lexer':
- LogService.warn(`
- Please notice "es-module-lexer" support is deprecated.
- For parsing javascript, "babel" is recommended.`);
- return this._getEsModuleLexerOutput(code);
- }
- } catch (e) {
- LogService.error(`Error when parsing "${filePath}":/n${e}`);
- }
- }
-}
-
-module.exports = { AstService };
diff --git a/packages-node/providence-analytics/src/program/types/core/Analyzer.d.ts b/packages-node/providence-analytics/src/program/types/core/Analyzer.d.ts
index eb73bae62..79c2a691e 100644
--- a/packages-node/providence-analytics/src/program/types/core/Analyzer.d.ts
+++ b/packages-node/providence-analytics/src/program/types/core/Analyzer.d.ts
@@ -8,6 +8,7 @@ import {
Project,
GatherFilesConfig,
SpecifierName,
+ QueryOutput,
} from './index';
/**
@@ -27,21 +28,23 @@ export interface Meta {
export interface AnalyzerMeta {
name: AnalyzerName;
requiredAst: RequiredAst;
- /** a unique hash based on target, reference and configuration */
+ /* a unique hash based on target, reference and configuration */
identifier: ImportOrExportId;
- /** target project meta object */
+ /* target project meta object */
targetProject: Project;
- /** reference project meta object */
+ /* reference project meta object */
referenceProject?: Project;
- /** the configuration used for this particular analyzer run */
+ /* the configuration used for this particular analyzer run */
configuration: object;
+ /* whether it was cached in file system or not */
+ __fromCache?: boolean;
}
export interface AnalyzerQueryResult extends QueryResult {
/** meta info object */
meta: Meta;
/** array of AST traversal output, per project file */
- queryOutput: any[];
+ queryOutput: QueryOutput;
}
export interface FindAnalyzerQueryResult extends AnalyzerQueryResult {
@@ -58,7 +61,9 @@ export interface FindAnalyzerOutputFile {
export interface AnalyzerConfig {
/** search target project path */
targetProjectPath: PathFromSystemRoot;
- gatherFilesConfig: GatherFilesConfig;
+ gatherFilesConfig?: GatherFilesConfig;
+ gatherFilesConfigReference?: GatherFilesConfig;
+ skipCheckMatchCompatibility?: boolean;
}
export interface MatchAnalyzerConfig extends AnalyzerConfig {
diff --git a/packages-node/providence-analytics/src/program/types/core/QueryService.d.ts b/packages-node/providence-analytics/src/program/types/core/QueryService.d.ts
index 86d50f0bc..2d0ec3be2 100644
--- a/packages-node/providence-analytics/src/program/types/core/QueryService.d.ts
+++ b/packages-node/providence-analytics/src/program/types/core/QueryService.d.ts
@@ -1,6 +1,6 @@
import { AnalyzerName, Feature, AnalyzerConfig, PathRelativeFromProjectRoot } from './index';
-import { Analyzer } from '../../analyzers/helpers/Analyzer';
-export { Analyzer } from '../../analyzers/helpers/Analyzer';
+import { Analyzer } from '../../core/Analyzer';
+export { Analyzer } from '../../core/Analyzer';
/**
* Type of the query. Currently only "ast-analyzer" supported
@@ -38,7 +38,7 @@ export interface QueryOutputEntry {
file: PathRelativeFromProjectRoot;
}
-export type QueryOutput = QueryOutputEntry[];
+export type QueryOutput = QueryOutputEntry[] | '[no-dependency]' | '[no-matched-version]';
export interface QueryResult {
queryOutput: QueryOutput;
diff --git a/packages-node/providence-analytics/src/program/types/core/core.d.ts b/packages-node/providence-analytics/src/program/types/core/core.d.ts
index 3d1d685a7..3328b5223 100644
--- a/packages-node/providence-analytics/src/program/types/core/core.d.ts
+++ b/packages-node/providence-analytics/src/program/types/core/core.d.ts
@@ -1,3 +1,5 @@
+import { File } from '@babel/types';
+
/**
* The name of a variable in a local context. Examples:
* - 'b': (`import {a as b } from 'c';`)
@@ -140,6 +142,11 @@ export interface ProjectInputDataWithMeta {
project: Project;
entries: { file: PathRelativeFromProjectRoot; context: { code: string } }[];
}
+
+export interface ProjectInputDataWithAstMeta extends ProjectInputDataWithMeta {
+ entries: { file: PathRelativeFromProjectRoot; ast: File; context: { code: string } }[];
+}
+
/**
* See: https://www.npmjs.com/package/anymatch
* Allows negations as well. See: https://www.npmjs.com/package/is-negated-glob
@@ -149,3 +156,33 @@ export interface ProjectInputDataWithMeta {
* - 'scripts/vendor/react.js'
*/
export type AnyMatchString = string;
+
+export type ProvidenceConfig = {
+ /* Whether analyzer should be run or a grep should be performed */
+ queryMethod: 'ast' | 'grep';
+ /* Search target projects. For instance ['/path/to/app-a', '/path/to/app-b', ... '/path/to/app-z'] */
+ targetProjectPaths: PathFromSystemRoot[];
+ /* Reference projects. Needed for 'match analyzers', having `requiresReference: true`. For instance ['/path/to/lib1', '/path/to/lib2'] */
+ referenceProjectPaths: PathFromSystemRoot[];
+ /* When targetProjectPaths are dependencies of other projects (their 'roots') */
+ targetProjectRootPaths: PathFromSystemRoot[];
+ gatherFilesConfig: GatherFilesConfig;
+ gatherFilesConfigReference: GatherFilesConfig;
+ report: boolean;
+ debugEnabled: boolean;
+ measurePerformance: boolean;
+ writeLogFile: boolean;
+ skipCheckMatchCompatibility: boolean;
+};
+
+/**
+ * Representation of package.json with only those keys relevant for Providence
+ */
+export type PackageJson = {
+ name: string;
+ version: string;
+ files?: PathRelativeFromProjectRoot[];
+ dependencies?: { [dependency: string]: string };
+ devDependencies?: { [dependency: string]: string };
+ workspaces?: string[];
+};
diff --git a/packages-node/providence-analytics/src/program/types/index.d.ts b/packages-node/providence-analytics/src/program/types/index.d.ts
new file mode 100644
index 000000000..57aa6fc5e
--- /dev/null
+++ b/packages-node/providence-analytics/src/program/types/index.d.ts
@@ -0,0 +1,3 @@
+export * from './core';
+export * from './analyzers';
+export * from './utils';
diff --git a/packages-node/providence-analytics/src/program/types/utils/memoize.d.ts b/packages-node/providence-analytics/src/program/types/utils/memoize.d.ts
new file mode 100644
index 000000000..9a8761c71
--- /dev/null
+++ b/packages-node/providence-analytics/src/program/types/utils/memoize.d.ts
@@ -0,0 +1 @@
+export type MemoizeFunction = (fn: T, storage?: object) => T;
diff --git a/packages-node/providence-analytics/src/program/utils/async-array-utils.js b/packages-node/providence-analytics/src/program/utils/async-array-utils.js
deleted file mode 100644
index 795e493d2..000000000
--- a/packages-node/providence-analytics/src/program/utils/async-array-utils.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Readable way to do an async forEach
- * Since predictability matters, all array items will be handled in a queue,
- * one after another
- * @param {any[]} array
- * @param {function} callback
- */
-async function aForEach(array, callback) {
- for (let i = 0; i < array.length; i += 1) {
- // eslint-disable-next-line no-await-in-loop
- await callback(array[i], i);
- }
-}
-/**
- * Readable way to do an async forEach
- * If predictability does not matter, this method will traverse array items concurrently,
- * leading to a better performance
- * @param {any[]} array
- * @param {(value:any, index:number) => {}} callback
- */
-async function aForEachNonSequential(array, callback) {
- return Promise.all(array.map(callback));
-}
-/**
- * Readable way to do an async map
- * Since predictability is crucial for a map, all array items will be handled in a queue,
- * one after anotoher
- * @param {Array} array
- * @param {(param:any, i:number) => any} callback
- */
-async function aMap(array, callback) {
- const mappedResults = [];
- for (let i = 0; i < array.length; i += 1) {
- // eslint-disable-next-line no-await-in-loop
- const resolvedCb = await callback(array[i], i);
- mappedResults.push(resolvedCb);
- }
- return mappedResults;
-}
-
-module.exports = { aForEach, aMap, aForEachNonSequential };
diff --git a/packages-node/providence-analytics/src/program/utils/get-current-dir.mjs b/packages-node/providence-analytics/src/program/utils/get-current-dir.mjs
new file mode 100644
index 000000000..a46977b70
--- /dev/null
+++ b/packages-node/providence-analytics/src/program/utils/get-current-dir.mjs
@@ -0,0 +1,9 @@
+import { dirname } from 'path';
+import { fileURLToPath } from 'url';
+
+/**
+ * @param {string} importMetaUrl should be import.meta.url
+ */
+export function getCurrentDir(importMetaUrl) {
+ return dirname(fileURLToPath(importMetaUrl));
+}
diff --git a/packages-node/providence-analytics/src/program/utils/get-source-code-fragment-of-declaration.js b/packages-node/providence-analytics/src/program/utils/get-source-code-fragment-of-declaration.js
index 6edbbba78..8602391ad 100644
--- a/packages-node/providence-analytics/src/program/utils/get-source-code-fragment-of-declaration.js
+++ b/packages-node/providence-analytics/src/program/utils/get-source-code-fragment-of-declaration.js
@@ -1,7 +1,7 @@
const fs = require('fs');
const path = require('path');
const babelTraversePkg = require('@babel/traverse');
-const { AstService } = require('../services/AstService.js');
+const { AstService } = require('../core/AstService.js');
const { trackDownIdentifier } = require('../analyzers/helpers/track-down-identifier.js');
const { toPosixPath } = require('./to-posix-path.js');
diff --git a/packages-node/providence-analytics/src/program/utils/lit-to-obj.js b/packages-node/providence-analytics/src/program/utils/lit-to-obj.js
deleted file mode 100644
index 9e4dfae09..000000000
--- a/packages-node/providence-analytics/src/program/utils/lit-to-obj.js
+++ /dev/null
@@ -1,23 +0,0 @@
-// import htm from 'htm';
-const htm = require('htm');
-
-function convertToObj(type, props, ...children) {
- return { type, props, children };
-}
-
-/**
- * @desc
- * Used for parsing lit-html templates inside ASTs
- * @returns {type, props, children}
- *
- * @example
- * litToObj`Hello world!
`;
- * // {
- * // type: 'h1',
- * // props: { .id: 'hello' },
- * // children: ['Hello world!']
- * // }
- */
-const litToObj = htm.bind(convertToObj);
-
-module.exports = litToObj;
diff --git a/packages-node/providence-analytics/src/program/utils/memoize.js b/packages-node/providence-analytics/src/program/utils/memoize.js
index dd32cc158..76fdfd831 100644
--- a/packages-node/providence-analytics/src/program/utils/memoize.js
+++ b/packages-node/providence-analytics/src/program/utils/memoize.js
@@ -1,9 +1,17 @@
-const { InputDataService } = require('../services/InputDataService.js');
+const memoizeConfig = {
+ isCacheDisabled: false,
+};
+/**
+ * @param {object|any[]|string} arg
+ */
function isObject(arg) {
return !Array.isArray(arg) && typeof arg === 'object';
}
+/**
+ * @param {object|any[]|string} arg
+ */
function createCachableArg(arg) {
if (isObject(arg)) {
try {
@@ -17,7 +25,7 @@ function createCachableArg(arg) {
/**
* @param {function} functionToMemoize
- * @param {{ storage:object; serializeObjects: boolean }} [opts]
+ * @param {{ storage?:object; serializeObjects?: boolean }} opts
*/
function memoize(functionToMemoize, { storage = {}, serializeObjects = false } = {}) {
// eslint-disable-next-line func-names
@@ -27,7 +35,7 @@ function memoize(functionToMemoize, { storage = {}, serializeObjects = false } =
const cachableArgs = !serializeObjects ? args : args.map(createCachableArg);
// Allow disabling of cache for testing purposes
// @ts-ignore
- if (!InputDataService.cacheDisabled && cachableArgs in storage) {
+ if (!memoizeConfig.isCacheDisabled && cachableArgs in storage) {
// @ts-ignore
return storage[cachableArgs];
}
@@ -42,4 +50,5 @@ function memoize(functionToMemoize, { storage = {}, serializeObjects = false } =
module.exports = {
memoize,
+ memoizeConfig,
};
diff --git a/packages-node/providence-analytics/src/program/utils/get-providence-conf.mjs b/packages-node/providence-analytics/src/program/utils/providence-conf-util.mjs
similarity index 91%
rename from packages-node/providence-analytics/src/program/utils/get-providence-conf.mjs
rename to packages-node/providence-analytics/src/program/utils/providence-conf-util.mjs
index 9423e3456..2e291cb13 100644
--- a/packages-node/providence-analytics/src/program/utils/get-providence-conf.mjs
+++ b/packages-node/providence-analytics/src/program/utils/providence-conf-util.mjs
@@ -5,7 +5,7 @@ import { pathToFileURL } from 'url';
/**
* @returns {Promise