chore: move to swc + esm

This commit is contained in:
Thijs Louisse 2023-05-09 16:08:47 +02:00
parent 60032a2df0
commit 0315b33acd
94 changed files with 2568 additions and 2312 deletions

View file

@ -19,7 +19,7 @@ module.exports = {
files: [ files: [
'**/test-suites/**/*.js', '**/test-suites/**/*.js',
'**/test/**/*.js', '**/test/**/*.js',
'**/test-node/**/*.js', '**/test-node/**/*.{j,mj}s',
'**/demo/**/*.js', '**/demo/**/*.js',
'**/docs/**/*.js', '**/docs/**/*.js',
'**/*.config.js', '**/*.config.js',

View file

@ -1,6 +1,5 @@
import { OverlaysManager } from 'overlays'; import { OverlaysManager , OverlaysManager as OverlaysManager2 } from 'overlays';
import { singletonManager } from 'singleton-manager'; import { singletonManager } from 'singleton-manager';
import { OverlaysManager as OverlaysManager2 } from './node_modules/page-b/node_modules/overlays/index.js';
let compatibleManager1; let compatibleManager1;
let compatibleManager2; let compatibleManager2;

View file

@ -44,70 +44,64 @@
"changeset": "^0.2.6" "changeset": "^0.2.6"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.10.1", "@babel/core": "^7.21.3",
"@bundled-es-modules/fetch-mock": "^6.5.2", "@bundled-es-modules/fetch-mock": "^6.5.2",
"@changesets/cli": "^2.26.1", "@changesets/cli": "^2.26.1",
"@custom-elements-manifest/analyzer": "^0.5.7", "@custom-elements-manifest/analyzer": "^0.8.0",
"@open-wc/building-rollup": "^1.2.1", "@open-wc/building-rollup": "^1.10.0",
"@open-wc/eslint-config": "^7.0.0", "@open-wc/eslint-config": "^10.0.0",
"@open-wc/testing": "^3.1.2", "@open-wc/testing": "^3.1.7",
"@open-wc/testing-helpers": "^2.1.2", "@open-wc/testing-helpers": "^2.2.0",
"@rocket/blog": "^0.4.0", "@rocket/blog": "^0.4.0",
"@rocket/cli": "^0.10.1", "@rocket/cli": "^0.10.2",
"@rocket/launch": "^0.6.0", "@rocket/launch": "^0.6.0",
"@rocket/search": "^0.5.1", "@rocket/search": "^0.5.1",
"@types/chai-as-promised": "^7.1.5", "@types/chai-as-promised": "^7.1.5",
"@types/chai-dom": "^0.0.8", "@types/chai-dom": "^0.0.8",
"@types/convert-source-map": "^1.5.1", "@types/convert-source-map": "^1.5.2",
"@types/fs-extra": "^9.0.7", "@types/fs-extra": "^9.0.13",
"@types/glob": "^7.1.3", "@types/glob": "^7.1.3",
"@types/istanbul-reports": "^3.0.0", "@types/istanbul-reports": "^3.0.1",
"@types/mocha": "^10.0.0", "@types/mocha": "^10.0.1",
"@types/prettier": "^2.2.1", "@types/prettier": "^2.7.2",
"@web/dev-server": "^0.1.8", "@web/dev-server": "^0.1.37",
"@web/dev-server-legacy": "^0.1.7", "@web/dev-server-legacy": "^0.1.7",
"@web/test-runner": "^0.13.7", "@web/test-runner": "^0.15.2",
"@web/test-runner-browserstack": "^0.5.0", "@web/test-runner-browserstack": "^0.5.1",
"@web/test-runner-commands": "^0.6.1", "@web/test-runner-commands": "^0.6.5",
"@web/test-runner-playwright": "^0.8.8", "@web/test-runner-playwright": "^0.9.0",
"@webcomponents/scoped-custom-element-registry": "^0.0.5", "@webcomponents/scoped-custom-element-registry": "^0.0.8",
"@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/lockfile": "^1.1.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"bundlesize": "^1.0.0-beta.2", "bundlesize": "^1.0.0-beta.2",
"cem-plugin-vs-code-custom-data-generator": "^1.4.1", "cem-plugin-vs-code-custom-data-generator": "^1.4.1",
"chai": "^4.2.0", "chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chalk": "^4.1.0",
"concurrently": "^5.2.0",
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"es6-promisify": "^6.1.1", "es6-promisify": "^6.1.1",
"eslint": "^8.26.0", "eslint": "^8.37.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-lit": "^1.6.1", "eslint-plugin-lit": "^1.8.2",
"eslint-plugin-lit-a11y": "^2.2.0", "eslint-plugin-lit-a11y": "^2.4.0",
"eslint-plugin-wc": "^1.3.2", "eslint-plugin-wc": "^1.4.0",
"globby": "^13.1.2", "globby": "^13.1.3",
"husky": "^6.0.0", "husky": "^6.0.0",
"lint-staged": "^10.0.0", "lint-staged": "^10.5.4",
"looks-same": "^7.2.3", "looks-same": "^7.3.0",
"markdownlint-cli": "^0.17.0", "markdownlint-cli": "^0.17.0",
"mermaid": "^9.3.0",
"minimist": "^1.2.6", "minimist": "^1.2.6",
"mkdirp-promise": "^5.0.1", "mkdirp-promise": "^5.0.1",
"mocha": "^10.1.0", "mocha": "^10.1.0",
"mock-fs": "^5.1.2",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"nyc": "^15.0.0", "playwright": "^1.32.1",
"playwright": "^1.20.0",
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^2.0.5", "prettier": "^2.0.5",
"prettier-package-json": "^2.1.3", "prettier-package-json": "^2.1.3",
"remark-html": "^13.0.1", "remark-html": "^13.0.1",
"rimraf": "^2.6.3", "rimraf": "^2.6.3",
"rollup": "^2.0.0",
"semver": "^7.5.2", "semver": "^7.5.2",
"sinon": "^7.2.2", "rollup": "^2.79.1",
"sinon": "^7.5.0",
"ssl-root-cas": "^1.3.1", "ssl-root-cas": "^1.3.1",
"typescript": "~4.8.4", "typescript": "~4.8.4",
"whatwg-fetch": "^3.0.0", "whatwg-fetch": "^3.0.0",

View file

@ -1,5 +1,5 @@
/** /**
* @desc Can be called from a button click handler in order to let the end user download a file * Can be called from a button click handler in order to let the end user download a file
* @param {string} filename like 'overview.csv' * @param {string} filename like 'overview.csv'
* @param {string} content for instance a csv file * @param {string} content for instance a csv file
*/ */

View file

@ -1,15 +1,19 @@
import fs from 'fs'; import fs from 'fs';
import pathLib, { dirname } from 'path'; import pathLib from 'path';
import { fileURLToPath } from 'url';
import { startDevServer } from '@web/dev-server'; import { startDevServer } from '@web/dev-server';
import { ReportService } from '../src/program/core/ReportService.js'; import { ReportService } from '../src/program/core/ReportService.js';
import { providenceConfUtil } from '../src/program/utils/providence-conf-util.mjs'; import { providenceConfUtil } from '../src/program/utils/providence-conf-util.js';
import { getCurrentDir } from '../src/program/utils/get-current-dir.js';
const __dirname = dirname(fileURLToPath(import.meta.url)); /**
* @typedef {import('../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../types/index.js').GatherFilesConfig} GatherFilesConfig
* @typedef {import('../types/index.js').AnalyzerName} AnalyzerName
*/
/** /**
* Gets all results found in cache folder with all results * Gets all results found in cache folder with all results
* @param {{ supportedAnalyzers: `match-${string}`[], resultsPath: string }} options * @param {{ supportedAnalyzers?: `match-${string}`[], resultsPath?: string }} options
*/ */
async function getCachedProvidenceResults({ async function getCachedProvidenceResults({
supportedAnalyzers = ['match-imports', 'match-subclasses'], supportedAnalyzers = ['match-imports', 'match-subclasses'],
@ -34,6 +38,7 @@ async function getCachedProvidenceResults({
searchTargetDeps = content; searchTargetDeps = content;
} else { } else {
const analyzerName = fileName.split('_-_')[0]; const analyzerName = fileName.split('_-_')[0];
// @ts-ignore
if (!supportedAnalyzers.includes(analyzerName)) { if (!supportedAnalyzers.includes(analyzerName)) {
return; return;
} }
@ -48,7 +53,7 @@ async function getCachedProvidenceResults({
} }
/** /**
* @param {{ providenceConf: object; earchTargetDeps: object; resultFiles: string[]; }} * @param {{ providenceConf: object; providenceConfRaw:string; searchTargetDeps: object; resultFiles: string[]; }}
*/ */
function createMiddleWares({ providenceConf, providenceConfRaw, searchTargetDeps, resultFiles }) { function createMiddleWares({ providenceConf, providenceConfRaw, searchTargetDeps, resultFiles }) {
/** /**
@ -80,7 +85,10 @@ function createMiddleWares({ providenceConf, providenceConfRaw, searchTargetDeps
return res; return res;
} }
const pathFromServerRootToHere = `/${pathLib.relative(process.cwd(), __dirname)}`; const pathFromServerRootToHere = `/${pathLib.relative(
process.cwd(),
getCurrentDir(import.meta.url),
)}`;
return [ return [
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
@ -143,7 +151,7 @@ export async function createDashboardServerConfig() {
const moduleRoot = fromPackageRoot ? pathLib.resolve(process.cwd(), '../../') : process.cwd(); const moduleRoot = fromPackageRoot ? pathLib.resolve(process.cwd(), '../../') : process.cwd();
return { return {
appIndex: pathLib.resolve(__dirname, 'index.html'), appIndex: pathLib.resolve(getCurrentDir(import.meta.url), 'index.html'),
rootDir: moduleRoot, rootDir: moduleRoot,
nodeResolve: true, nodeResolve: true,
moduleDirs: pathLib.resolve(moduleRoot, 'node_modules'), moduleDirs: pathLib.resolve(moduleRoot, 'node_modules'),
@ -158,6 +166,7 @@ export async function createDashboardServerConfig() {
}; };
} }
/** @type {(value?: any) => void} */
let resolveLoaded; let resolveLoaded;
export const serverInstanceLoaded = new Promise(resolve => { export const serverInstanceLoaded = new Promise(resolve => {
resolveLoaded = resolve; resolveLoaded = resolve;

View file

@ -10,26 +10,27 @@
"url": "https://github.com/ing-bank/lion.git", "url": "https://github.com/ing-bank/lion.git",
"directory": "packages-node/providence-analytics" "directory": "packages-node/providence-analytics"
}, },
"type": "module",
"exports": { "exports": {
".": "./src/index.js", ".": "./src/index.js",
"./src/cli": "./src/cli/index.js", "./cli.js": "./src/cli/index.js",
"./utils.js": "./src/program/utils/index.js", "./utils.js": "./src/program/utils/index.js",
"./analyzers": "./src/program/analyzers/index.js", "./analyzers.js": "./src/program/analyzers/index.js",
"./docs/*": "./docs/*" "./docs/*": "./docs/*"
}, },
"main": "./src/index.js", "main": "./src/index.js",
"bin": { "bin": {
"providence": "./src/cli/index.mjs" "providence": "./src/cli/index.js"
}, },
"files": [ "files": [
"dashboard/src", "dashboard/src",
"src" "src"
], ],
"scripts": { "scripts": {
"dashboard": "node ./dashboard/server.mjs --run-server --serve-from-package-root", "dashboard": "node ./dashboard/server.js --run-server --serve-from-package-root",
"postinstall": "npx patch-package", "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", "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", "providence": "node --max-old-space-size=8192 ./src/cli/index.js",
"publish-docs": "node ../../packages-node/publish-docs/src/cli.js --github-url https://github.com/ing-bank/lion/ --git-root-dir ../../", "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", "prepublishOnly": "npm run publish-docs",
"test:node": "npm run test:node:unit && npm run test:node:e2e", "test:node": "npm run test:node:unit && npm run test:node:e2e",
@ -37,32 +38,35 @@
"test:node:unit": "mocha './test-node/**/*.test.{j,mj}s'" "test:node:unit": "mocha './test-node/**/*.test.{j,mj}s'"
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.10.1", "@babel/core": "^7.21.3",
"@babel/parser": "^7.5.5", "@babel/parser": "^7.21.3",
"@babel/plugin-proposal-class-properties": "^7.8.3", "@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-syntax-export-default-from": "^7.18.6", "@babel/plugin-syntax-export-default-from": "^7.18.6",
"@babel/plugin-syntax-import-assertions": "^7.18.6", "@babel/plugin-syntax-import-assertions": "^7.20.0",
"@babel/register": "^7.5.5", "@babel/register": "^7.21.0",
"@babel/traverse": "^7.23.2", "@babel/traverse": "^7.21.3",
"@babel/types": "^7.9.0", "@babel/types": "^7.21.3",
"@rollup/plugin-node-resolve": "^15.0.0", "@rollup/plugin-node-resolve": "^15.0.1",
"@web/dev-server": "^0.1.28", "@swc/core": "^1.3.42",
"anymatch": "^3.1.1", "@web/dev-server": "^0.1.37",
"chalk": "^4.1.0", "anymatch": "^3.1.3",
"commander": "^2.20.0", "commander": "^2.20.3",
"deepmerge": "^4.0.0", "glob": "^7.2.3",
"glob": "^7.1.6", "inquirer": "^9.1.5",
"inquirer": "^7.0.0",
"is-negated-glob": "^1.0.0", "is-negated-glob": "^1.0.0",
"lit-element": "~2.4.0", "lit-element": "~2.4.0",
"parse5": "^5.1.1", "parse5": "^7.1.2",
"read-package-tree": "5.3.1", "read-package-tree": "5.3.1",
"semver": "^7.5.2", "semver": "^7.3.8",
"typescript": "~4.8.4" "swc-to-babel": "^1.26.0"
}, },
"devDependencies": { "devDependencies": {
"@web/dev-server-core": "^0.3.19", "@types/chai": "^4.3.4",
"mock-require": "^3.0.3" "@types/inquirer": "^9.0.3",
"@types/mocha": "^10.0.1",
"@web/dev-server-core": "^0.4.0",
"mock-require": "^3.0.3",
"mock-fs": "^5.2.0"
}, },
"keywords": [ "keywords": [
"analysis", "analysis",
@ -77,8 +81,5 @@
], ],
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
},
"imports": {
"#types": "./src/program/types"
} }
} }

View file

@ -9,7 +9,7 @@ export default {
majorVersion: 1, majorVersion: 1,
// These conditions will be run on overy filePath // These conditions will be run on overy filePath
categories: { categories: {
overlays: localFilePath => { overlays: (/** @type {string} */ localFilePath) => {
const names = ['dialog', 'tooltip']; const names = ['dialog', 'tooltip'];
const fromPackages = names.some(p => const fromPackages = names.some(p =>
localFilePath.startsWith(`./packages/ui/components/${p}`), localFilePath.startsWith(`./packages/ui/components/${p}`),

View file

@ -1,16 +1,16 @@
/* eslint-disable no-shadow */ /* eslint-disable no-shadow */
const pathLib = require('path'); import pathLib from 'path';
const child_process = require('child_process'); // eslint-disable-line camelcase import child_process from 'child_process'; // eslint-disable-line camelcase
const glob = require('glob'); import glob from 'glob';
const readPackageTree = require('../program/utils/read-package-tree-with-bower-support.js'); import readPackageTree from '../program/utils/read-package-tree-with-bower-support.js';
const { LogService } = require('../program/core/LogService.js'); import { LogService } from '../program/core/LogService.js';
const { toPosixPath } = require('../program/utils/to-posix-path.js'); import { toPosixPath } from '../program/utils/to-posix-path.js';
/** /**
* @param {any[]} arr * @param {any[]} arr
* @returns {any[]} * @returns {any[]}
*/ */
function flatten(arr) { export function flatten(arr) {
return Array.prototype.concat.apply([], arr); return Array.prototype.concat.apply([], arr);
} }
@ -18,7 +18,7 @@ function flatten(arr) {
* @param {string} v * @param {string} v
* @returns {string[]} * @returns {string[]}
*/ */
function csToArray(v) { export function csToArray(v) {
return v.split(',').map(v => v.trim()); return v.split(',').map(v => v.trim());
} }
@ -26,11 +26,16 @@ function csToArray(v) {
* @param {string} v like 'js,html' * @param {string} v like 'js,html'
* @returns {string[]} like ['.js', '.html'] * @returns {string[]} like ['.js', '.html']
*/ */
function extensionsFromCs(v) { export function extensionsFromCs(v) {
return csToArray(v).map(v => `.${v}`); return csToArray(v).map(v => `.${v}`);
} }
function setQueryMethod(m) { /**
*
* @param {*} m
* @returns
*/
export function setQueryMethod(m) {
const allowedMehods = ['grep', 'ast']; const allowedMehods = ['grep', 'ast'];
if (allowedMehods.includes(m)) { if (allowedMehods.includes(m)) {
return m; return m;
@ -43,7 +48,7 @@ function setQueryMethod(m) {
* @param {string} t * @param {string} t
* @returns {string[]|undefined} * @returns {string[]|undefined}
*/ */
function pathsArrayFromCs(t, cwd = process.cwd()) { export function pathsArrayFromCs(t, cwd = process.cwd()) {
if (!t) { if (!t) {
return undefined; return undefined;
} }
@ -72,10 +77,10 @@ function pathsArrayFromCs(t, cwd = process.cwd()) {
* @param {string} [cwd] * @param {string} [cwd]
* @returns {string[]|undefined} * @returns {string[]|undefined}
*/ */
function pathsArrayFromCollectionName( export function pathsArrayFromCollectionName(
name, name,
collectionType = 'search-target', collectionType = 'search-target',
eCfg, eCfg = undefined,
cwd = process.cwd(), cwd = process.cwd(),
) { ) {
let collection; let collection;
@ -93,10 +98,10 @@ function pathsArrayFromCollectionName(
/** /**
* @param {string} processArgStr * @param {string} processArgStr
* @param {object} [opts] * @param {object} [opts]
* @returns {Promise<{ code:string; number:string }>} * @returns {Promise<{ code:number; output:string }>}
* @throws {Error} * @throws {Error}
*/ */
function spawnProcess(processArgStr, opts) { export function spawnProcess(processArgStr, opts) {
const processArgs = processArgStr.split(' '); const processArgs = processArgStr.split(' ');
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
const proc = child_process.spawn(processArgs[0], processArgs.slice(1), opts); const proc = child_process.spawn(processArgs[0], processArgs.slice(1), opts);
@ -123,7 +128,7 @@ function spawnProcess(processArgStr, opts) {
* @param {string} cwd * @param {string} cwd
* @returns {string[]} * @returns {string[]}
*/ */
function targetDefault(cwd) { export function targetDefault(cwd) {
return [toPosixPath(cwd)]; return [toPosixPath(cwd)];
} }
@ -133,7 +138,11 @@ function targetDefault(cwd) {
* @param {string} [matchPattern] base for RegExp * @param {string} [matchPattern] base for RegExp
* @param {('npm'|'bower')[]} [modes] * @param {('npm'|'bower')[]} [modes]
*/ */
async function appendProjectDependencyPaths(rootPaths, matchPattern, modes = ['npm', 'bower']) { export async function appendProjectDependencyPaths(
rootPaths,
matchPattern,
modes = ['npm', 'bower'],
) {
let matchFn; let matchFn;
if (matchPattern) { if (matchPattern) {
if (matchPattern.startsWith('/') && matchPattern.endsWith('/')) { if (matchPattern.startsWith('/') && matchPattern.endsWith('/')) {
@ -181,12 +190,13 @@ async function appendProjectDependencyPaths(rootPaths, matchPattern, modes = ['n
* Relevant when '--target-dependencies' is supplied. * Relevant when '--target-dependencies' is supplied.
* @param {string[]} searchTargetPaths * @param {string[]} searchTargetPaths
*/ */
async function installDeps(searchTargetPaths) { export async function installDeps(searchTargetPaths) {
for (const targetPath of searchTargetPaths) { for (const targetPath of searchTargetPaths) {
LogService.info(`Installing npm dependencies for ${pathLib.basename(targetPath)}`); LogService.info(`Installing npm dependencies for ${pathLib.basename(targetPath)}`);
try { try {
await spawnProcess('npm i --no-progress', { cwd: targetPath }); await spawnProcess('npm i --no-progress', { cwd: targetPath });
} catch (e) { } catch (e) {
// @ts-expect-error
LogService.error(e); LogService.error(e);
} }
@ -194,12 +204,13 @@ async function installDeps(searchTargetPaths) {
try { try {
await spawnProcess(`bower i --production --force-latest`, { cwd: targetPath }); await spawnProcess(`bower i --production --force-latest`, { cwd: targetPath });
} catch (e) { } catch (e) {
// @ts-expect-error
LogService.error(e); LogService.error(e);
} }
} }
} }
module.exports = { export const _cliHelpersModule = {
csToArray, csToArray,
extensionsFromCs, extensionsFromCs,
setQueryMethod, setQueryMethod,

View file

@ -1,25 +1,30 @@
import child_process from 'child_process'; // eslint-disable-line camelcase import child_process from 'child_process'; // eslint-disable-line camelcase
import pathLib from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import commander from 'commander'; import commander from 'commander';
import providenceModule from '../program/providence.js';
import { LogService } from '../program/core/LogService.js'; import { LogService } from '../program/core/LogService.js';
import { QueryService } from '../program/core/QueryService.js'; import { QueryService } from '../program/core/QueryService.js';
import { InputDataService } from '../program/core/InputDataService.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 { toPosixPath } from '../program/utils/to-posix-path.js';
import { getCurrentDir } from '../program/utils/get-current-dir.mjs'; import { getCurrentDir } from '../program/utils/get-current-dir.js';
import { dashboardServer } from '../../dashboard/server.mjs'; import { dashboardServer } from '../../dashboard/server.js';
import { _providenceModule } from '../program/providence.js';
const { version } = JSON.parse( import { _cliHelpersModule } from './cli-helpers.js';
fs.readFileSync(pathLib.resolve(getCurrentDir(import.meta.url), '../../package.json'), 'utf8'), import { _extendDocsModule } from './launch-providence-with-extend-docs.js';
); import { _promptAnalyzerMenuModule } from './prompt-analyzer-menu.js';
const { extensionsFromCs, setQueryMethod, targetDefault, installDeps } = cliHelpers;
/** /**
* @param {{cwd?:string; argv: string[]; providenceConf?: object}} cfg * @typedef {import('../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('../../types/index.js').ProvidenceCliConf} ProvidenceCliConf
*/
const { version } = JSON.parse(
fs.readFileSync(path.resolve(getCurrentDir(import.meta.url), '../../package.json'), 'utf8'),
);
const { extensionsFromCs, setQueryMethod, targetDefault, installDeps } = _cliHelpersModule;
/**
* @param {{cwd?:string; argv?: string[]; providenceConf?: Partial<ProvidenceCliConf>}} cfg
*/ */
export async function cli({ cwd = process.cwd(), providenceConf, argv = process.argv }) { export async function cli({ cwd = process.cwd(), providenceConf, argv = process.argv }) {
/** @type {(value: any) => void} */ /** @type {(value: any) => void} */
@ -44,6 +49,13 @@ export async function cli({ cwd = process.cwd(), providenceConf, argv = process.
// TODO: change back to "InputDataService.getExternalConfig();" once full package ESM // TODO: change back to "InputDataService.getExternalConfig();" once full package ESM
const externalConfig = providenceConf; const externalConfig = providenceConf;
/**
* @param {'search-query'|'feature-query'|'analyzer-query'} searchMode
* @param {{regexString: string}} regexSearchOptions
* @param {{queryString: string}} featureOptions
* @param {{name:AnalyzerName; config:object;promptOptionalConfig:object}} analyzerOptions
* @returns
*/
async function getQueryConfigAndMeta( async function getQueryConfigAndMeta(
/* eslint-disable no-shadow */ /* eslint-disable no-shadow */
searchMode, searchMode,
@ -66,11 +78,12 @@ export async function cli({ cwd = process.cwd(), providenceConf, argv = process.
} else if (searchMode === 'analyzer-query') { } else if (searchMode === 'analyzer-query') {
let { name, config } = analyzerOptions; let { name, config } = analyzerOptions;
if (!name) { if (!name) {
const answers = await promptModule.promptAnalyzerMenu(); const answers = await _promptAnalyzerMenuModule.promptAnalyzerMenu();
name = answers.analyzerName; name = answers.analyzerName;
} }
if (!config) { if (!config) {
const answers = await promptModule.promptAnalyzerConfigMenu( const answers = await _promptAnalyzerMenuModule.promptAnalyzerConfigMenu(
name, name,
analyzerOptions.promptOptionalConfig, analyzerOptions.promptOptionalConfig,
); );
@ -79,7 +92,7 @@ export async function cli({ cwd = process.cwd(), providenceConf, argv = process.
// Will get metaConfig from ./providence.conf.js // Will get metaConfig from ./providence.conf.js
const metaConfig = externalConfig ? externalConfig.metaConfig : {}; const metaConfig = externalConfig ? externalConfig.metaConfig : {};
config = { ...config, metaConfig }; config = { ...config, metaConfig };
queryConfig = QueryService.getQueryConfigFromAnalyzer(name, config); queryConfig = await QueryService.getQueryConfigFromAnalyzer(name, config);
queryMethod = 'ast'; queryMethod = 'ast';
} else { } else {
LogService.error('Please define a feature, analyzer or search'); LogService.error('Please define a feature, analyzer or search');
@ -108,7 +121,7 @@ export async function cli({ cwd = process.cwd(), providenceConf, argv = process.
*/ */
let totalSearchTargets; let totalSearchTargets;
if (commander.targetDependencies !== undefined) { if (commander.targetDependencies !== undefined) {
totalSearchTargets = await cliHelpers.appendProjectDependencyPaths( totalSearchTargets = await _cliHelpersModule.appendProjectDependencyPaths(
searchTargetPaths, searchTargetPaths,
commander.targetDependencies, commander.targetDependencies,
); );
@ -121,7 +134,7 @@ export async function cli({ cwd = process.cwd(), providenceConf, argv = process.
// we do not test against ourselves... // we do not test against ourselves...
// - // -
providenceModule.providence(queryConfig, { _providenceModule.providence(queryConfig, {
gatherFilesConfig: { gatherFilesConfig: {
extensions: commander.extensions, extensions: commander.extensions,
allowlistMode: commander.allowlistMode, allowlistMode: commander.allowlistMode,
@ -141,11 +154,15 @@ export async function cli({ cwd = process.cwd(), providenceConf, argv = process.
skipCheckMatchCompatibility: commander.skipCheckMatchCompatibility, skipCheckMatchCompatibility: commander.skipCheckMatchCompatibility,
measurePerformance: commander.measurePerf, measurePerformance: commander.measurePerf,
addSystemPathsInResult: commander.addSystemPaths, addSystemPathsInResult: commander.addSystemPaths,
fallbackToBabel: commander.fallbackToBabel,
}); });
} }
/**
* @param {{update:boolean; deps:boolean;createVersionHistory:boolean}} options
*/
async function manageSearchTargets(options) { async function manageSearchTargets(options) {
const basePath = pathLib.join(__dirname, '../..'); const basePath = path.join(__dirname, '../..');
if (options.update) { if (options.update) {
LogService.info('git submodule update --init --recursive'); LogService.info('git submodule update --init --recursive');
@ -175,7 +192,7 @@ export async function cli({ cwd = process.cwd(), providenceConf, argv = process.
'-t, --search-target-paths [targets]', '-t, --search-target-paths [targets]',
`path(s) to project(s) on which analysis/querying should take place. Requires `path(s) to project(s) on which analysis/querying should take place. Requires
a list of comma seperated values relative to project root`, a list of comma seperated values relative to project root`,
v => cliHelpers.pathsArrayFromCs(v, cwd), v => _cliHelpersModule.pathsArrayFromCs(v, cwd),
targetDefault(cwd), targetDefault(cwd),
) )
.option( .option(
@ -183,29 +200,29 @@ export async function cli({ cwd = process.cwd(), providenceConf, argv = process.
`path(s) to project(s) which serve as a reference (applicable for certain analyzers like `path(s) to project(s) which serve as a reference (applicable for certain analyzers like
'match-imports'). Requires a list of comma seperated values relative to 'match-imports'). Requires a list of comma seperated values relative to
project root (like 'node_modules/lion-based-ui, node_modules/lion-based-ui-labs').`, project root (like 'node_modules/lion-based-ui, node_modules/lion-based-ui-labs').`,
v => cliHelpers.pathsArrayFromCs(v, cwd), v => _cliHelpersModule.pathsArrayFromCs(v, cwd),
InputDataService.referenceProjectPaths, InputDataService.referenceProjectPaths,
) )
.option('-a, --allowlist [allowlist]', `allowlisted paths, like 'src/**/*, packages/**/*'`, v => .option('-a, --allowlist [allowlist]', `allowlisted paths, like 'src/**/*, packages/**/*'`, v =>
cliHelpers.csToArray(v), _cliHelpersModule.csToArray(v),
) )
.option( .option(
'--allowlist-reference [allowlist-reference]', '--allowlist-reference [allowlist-reference]',
`allowed paths for reference, like 'src/**/*, packages/**/*'`, `allowed paths for reference, like 'src/**/*, packages/**/*'`,
v => cliHelpers.csToArray(v), v => _cliHelpersModule.csToArray(v),
) )
.option( .option(
'--search-target-collection [collection-name]', '--search-target-collection [collection-name]',
`path(s) to project(s) which serve as a reference (applicable for certain analyzers like `path(s) to project(s) which serve as a reference (applicable for certain analyzers like
'match-imports'). Should be a collection defined in providence.conf.js as paths relative to 'match-imports'). Should be a collection defined in providence.conf.js as paths relative to
project root.`, project root.`,
v => cliHelpers.pathsArrayFromCollectionName(v, 'search-target', externalConfig), v => _cliHelpersModule.pathsArrayFromCollectionName(v, 'search-target', externalConfig),
) )
.option( .option(
'--reference-collection [collection-name]', '--reference-collection [collection-name]',
`path(s) to project(s) on which analysis/querying should take place. Should be a collection `path(s) to project(s) on which analysis/querying should take place. Should be a collection
defined in providence.conf.js as paths relative to project root.`, defined in providence.conf.js as paths relative to project root.`,
v => cliHelpers.pathsArrayFromCollectionName(v, 'reference', externalConfig), v => _cliHelpersModule.pathsArrayFromCollectionName(v, 'reference', externalConfig),
) )
.option('--write-log-file', `Writes all logs to 'providence.log' file`) .option('--write-log-file', `Writes all logs to 'providence.log' file`)
.option( .option(
@ -236,7 +253,11 @@ export async function cli({ cwd = process.cwd(), providenceConf, argv = process.
`skips semver checks, handy for forward compatible libs or libs below v1`, `skips semver checks, handy for forward compatible libs or libs below v1`,
) )
.option('--measure-perf', 'Logs the completion time in seconds') .option('--measure-perf', 'Logs the completion time in seconds')
.option('--add-system-paths', 'Adds system paths to results'); .option('--add-system-paths', 'Adds system paths to results')
.option(
'--fallback-to-babel',
'Uses babel instead of swc. This will be slower, but guaranteed to be 100% compatible with @babel/generate and @babel/traverse',
);
commander commander
.command('search <regex>') .command('search <regex>')
@ -302,7 +323,7 @@ export async function cli({ cwd = process.cwd(), providenceConf, argv = process.
.option( .option(
'--output-folder [output-folder]', '--output-folder [output-folder]',
`This is the file path where the result file "providence-extend-docs-data.json" will be written to`, `This is the file path where the result file "providence-extend-docs-data.json" will be written to`,
p => toPosixPath(pathLib.resolve(process.cwd(), p.trim())), p => toPosixPath(path.resolve(process.cwd(), p.trim())),
process.cwd(), process.cwd(),
) )
.action(options => { .action(options => {
@ -315,7 +336,7 @@ export async function cli({ cwd = process.cwd(), providenceConf, argv = process.
process.exit(1); process.exit(1);
} }
const prefixCfg = { from: options.prefixFrom, to: options.prefixTo }; const prefixCfg = { from: options.prefixFrom, to: options.prefixTo };
extendDocsModule _extendDocsModule
.launchProvidenceWithExtendDocs({ .launchProvidenceWithExtendDocs({
referenceProjectPaths: commander.referencePaths, referenceProjectPaths: commander.referencePaths,
prefixCfg, prefixCfg,

View file

@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
import { cli } from './cli.mjs'; import { cli } from './cli.js';
import { providenceConfUtil } from '../program/utils/providence-conf-util.mjs'; import { providenceConfUtil } from '../program/utils/providence-conf-util.js';
(async () => { (async () => {
// We need to provide config to cli, until whole package is rewritten as ESM. // We need to provide config to cli, until whole package is rewritten as ESM.

View file

@ -1,16 +1,17 @@
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
const fs = require('fs'); import fs from 'fs';
const pathLib = require('path'); import pathLib from 'path';
const { performance } = require('perf_hooks'); import { performance } from 'perf_hooks';
const providenceModule = require('../program/providence.js'); import { _providenceModule } from '../program/providence.js';
const { QueryService } = require('../program/core/QueryService.js'); import { QueryService } from '../program/core/QueryService.js';
const { InputDataService } = require('../program/core/InputDataService.js'); import { InputDataService } from '../program/core/InputDataService.js';
const { LogService } = require('../program/core/LogService.js'); import { LogService } from '../program/core/LogService.js';
const { flatten } = require('./cli-helpers.js'); import { flatten } from './cli-helpers.js';
import MatchPathsAnalyzer from '../program/analyzers/match-paths.js';
/** /**
* @typedef {import('../program/types').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../program/types').GatherFilesConfig} GatherFilesConfig * @typedef {import('../../types/index.js').GatherFilesConfig} GatherFilesConfig
*/ */
/** /**
@ -24,7 +25,7 @@ const { flatten } = require('./cli-helpers.js');
* }} opts * }} opts
* @returns * @returns
*/ */
async function getExtendDocsResults({ export async function getExtendDocsResults({
referenceProjectPaths, referenceProjectPaths,
prefixCfg, prefixCfg,
extensions, extensions,
@ -34,8 +35,8 @@ async function getExtendDocsResults({
}) { }) {
const monoPkgs = InputDataService.getMonoRepoPackages(cwd); const monoPkgs = InputDataService.getMonoRepoPackages(cwd);
const results = await providenceModule.providence( const results = await _providenceModule.providence(
QueryService.getQueryConfigFromAnalyzer('match-paths', { prefix: prefixCfg }), await QueryService.getQueryConfigFromAnalyzer(MatchPathsAnalyzer, { prefix: prefixCfg }),
{ {
gatherFilesConfig: { gatherFilesConfig: {
extensions: extensions || /** @type {GatherFilesConfig['extensions']} */ (['.js']), extensions: extensions || /** @type {GatherFilesConfig['extensions']} */ (['.js']),
@ -98,7 +99,11 @@ async function getExtendDocsResults({
return queryOutputs; return queryOutputs;
} }
async function launchProvidenceWithExtendDocs({ /**
*
* @param {*} opts
*/
export async function launchProvidenceWithExtendDocs({
referenceProjectPaths, referenceProjectPaths,
prefixCfg, prefixCfg,
outputFolder, outputFolder,
@ -134,7 +139,7 @@ async function launchProvidenceWithExtendDocs({
LogService.info(`"extend-docs" completed in ${Math.round((t1 - t0) / 1000)} seconds`); LogService.info(`"extend-docs" completed in ${Math.round((t1 - t0) / 1000)} seconds`);
} }
module.exports = { export const _extendDocsModule = {
launchProvidenceWithExtendDocs, launchProvidenceWithExtendDocs,
getExtendDocsResults, getExtendDocsResults,
}; };

View file

@ -1,23 +1,31 @@
const fs = require('fs'); import fs from 'fs';
const pathLib = require('path'); import path from 'path';
const inquirer = require('inquirer'); import inquirer from 'inquirer';
const { default: traverse } = require('@babel/traverse'); import traverse from '@babel/traverse';
const { InputDataService } = require('../program/core/InputDataService.js'); import { InputDataService } from '../program/core/InputDataService.js';
const { AstService } = require('../program/core/AstService.js'); import { AstService } from '../program/core/AstService.js';
const { LogService } = require('../program/core/LogService.js'); import { LogService } from '../program/core/LogService.js';
const JsdocCommentParser = require('../program/utils/jsdoc-comment-parser.js'); import JsdocCommentParser from '../program/utils/jsdoc-comment-parser.js';
import { getCurrentDir } from '../program/utils/get-current-dir.js';
/**
* @typedef {import('../../types/index.js').TargetDepsObj} TargetDepsObj
* @typedef {import('../../types/index.js').TargetOrRefCollectionsObj} TargetOrRefCollectionsObj
* @typedef {import('../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../../types/index.js').AnalyzerName} AnalyzerName
*/
/** /**
* Extracts name, defaultValue, optional, type, desc from JsdocCommentParser.parse method * Extracts name, defaultValue, optional, type, desc from JsdocCommentParser.parse method
* result * result
* @param {string[]} jsdoc * @param {{tagName:string;tagValue:string}[]} jsdoc
* @returns {{ name:string, defaultValue:string, optional:boolean, type:string, desc:string }} * @returns {{ name:string, defaultValue:string, optional:boolean, type:string, desc:string }[]}
*/ */
function getPropsFromParsedJsDoc(jsdoc) { function getPropsFromParsedJsDoc(jsdoc) {
const jsdocProps = jsdoc.filter(p => p.tagName === '@property'); const jsdocProps = jsdoc.filter(p => p.tagName === '@property');
const options = jsdocProps.map(({ tagValue }) => { const options = jsdocProps.map(({ tagValue }) => {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const [_, type, nameOptionalDefault, desc] = tagValue.match(/\{(.*)\}\s*([^\s]*)\s*(.*)/); const [_, type, nameOptionalDefault, desc] = tagValue.match(/\{(.*)\}\s*([^\s]*)\s*(.*)/) || [];
let nameDefault = nameOptionalDefault; let nameDefault = nameOptionalDefault;
let optional = false; let optional = false;
if (nameOptionalDefault.startsWith('[') && nameOptionalDefault.endsWith(']')) { if (nameOptionalDefault.startsWith('[') && nameOptionalDefault.endsWith(']')) {
@ -30,21 +38,26 @@ function getPropsFromParsedJsDoc(jsdoc) {
return options; return options;
} }
/**
* @param {PathFromSystemRoot} file
*/
function getAnalyzerOptions(file) { function getAnalyzerOptions(file) {
const code = fs.readFileSync(file, 'utf8'); const code = fs.readFileSync(file, 'utf8');
const ast = AstService.getAst(code, 'babel', { filePath: file }); const babelAst = AstService.getAst(code, 'swc-to-babel', { filePath: file });
let commentNode; let commentNode;
traverse(ast, { traverse.default(babelAst, {
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
VariableDeclaration(path) { VariableDeclaration(astPath) {
if (!path.node.leadingComments) { const { node } = astPath;
if (!node.leadingComments) {
return; return;
} }
const decls = path.node.declarations || []; node.declarations.forEach(decl => {
decls.forEach(decl => { // @ts-expect-error
if (decl && decl.id && decl.id.name === 'cfg') { if (decl?.id?.name === 'cfg') {
[commentNode] = path.node.leadingComments; // eslint-disable-next-line prefer-destructuring
commentNode = node.leadingComments?.[0];
} }
}); });
}, },
@ -57,20 +70,33 @@ function getAnalyzerOptions(file) {
return undefined; return undefined;
} }
function gatherAnalyzers(dir, getConfigOptions) { /**
* @param {PathFromSystemRoot} dir
* @param {boolean} [shouldGetOptions]
*/
function gatherAnalyzers(dir, shouldGetOptions) {
return InputDataService.gatherFilesFromDir(dir, { depth: 0 }).map(file => { return InputDataService.gatherFilesFromDir(dir, { depth: 0 }).map(file => {
const analyzerObj = { file, name: pathLib.basename(file, '.js') }; const analyzerObj = { file, name: path.basename(file, '.js') };
if (getConfigOptions) { if (shouldGetOptions) {
analyzerObj.options = getAnalyzerOptions(file); analyzerObj.options = getAnalyzerOptions(file);
} }
return analyzerObj; return analyzerObj;
}); });
} }
async function promptAnalyzerConfigMenu( /**
*
* @param {AnalyzerName} analyzerName
* @param {*} promptOptionalConfig
* @param {PathFromSystemRoot} [dir]
* @returns
*/
export async function promptAnalyzerConfigMenu(
analyzerName, analyzerName,
promptOptionalConfig, promptOptionalConfig,
dir = pathLib.resolve(__dirname, '../program/analyzers'), dir = /** @type {PathFromSystemRoot} */ (
path.resolve(getCurrentDir(import.meta.url), '../program/analyzers')
),
) { ) {
const menuOptions = gatherAnalyzers(dir, true); const menuOptions = gatherAnalyzers(dir, true);
const analyzer = menuOptions.find(o => o.name === analyzerName); const analyzer = menuOptions.find(o => o.name === analyzerName);
@ -112,7 +138,11 @@ async function promptAnalyzerConfigMenu(
}; };
} }
async function promptAnalyzerMenu(dir = pathLib.resolve(__dirname, '../program/analyzers')) { export async function promptAnalyzerMenu(
dir = /** @type {PathFromSystemRoot} */ (
path.resolve(getCurrentDir(import.meta.url), '../program/analyzers')
),
) {
const menuOptions = gatherAnalyzers(dir); const menuOptions = gatherAnalyzers(dir);
const answers = await inquirer.prompt([ const answers = await inquirer.prompt([
{ {
@ -127,7 +157,7 @@ async function promptAnalyzerMenu(dir = pathLib.resolve(__dirname, '../program/a
}; };
} }
module.exports = { export const _promptAnalyzerMenuModule = {
promptAnalyzerMenu, promptAnalyzerMenu,
promptAnalyzerConfigMenu, promptAnalyzerConfigMenu,
}; };

View file

@ -1,7 +1,5 @@
const { providence } = require('./program/providence.js'); export { providence } from './program/providence.js';
const { QueryService } = require('./program/core/QueryService.js'); export { QueryService } from './program/core/QueryService.js';
const { LogService } = require('./program/core/LogService.js'); export { LogService } from './program/core/LogService.js';
const { InputDataService } = require('./program/core/InputDataService.js'); export { InputDataService } from './program/core/InputDataService.js';
const { AstService } = require('./program/core/AstService.js'); export { AstService } from './program/core/AstService.js';
module.exports = { providence, QueryService, LogService, InputDataService, AstService };

View file

@ -1,24 +1,27 @@
/* eslint-disable no-shadow, no-param-reassign */ /* eslint-disable no-shadow, no-param-reassign */
const pathLib = require('path'); import pathLib from 'path';
const t = require('@babel/types'); import t from '@babel/types';
const { default: traverse } = require('@babel/traverse'); import babelTraverse from '@babel/traverse';
const { Analyzer } = require('../core/Analyzer.js'); import { Analyzer } from '../core/Analyzer.js';
const { trackDownIdentifierFromScope } = require('./helpers/track-down-identifier.js'); import { trackDownIdentifierFromScope } from './helpers/track-down-identifier.js';
/** /**
* @typedef {import('@babel/types').File} File * @typedef {import('@babel/types').File} File
* @typedef {import('@babel/types').ClassMethod} ClassMethod * @typedef {import('@babel/types').ClassMethod} ClassMethod
* @typedef {import('../types/analyzers').FindClassesAnalyzerOutput} FindClassesAnalyzerOutput * @typedef {import('@babel/traverse').NodePath} NodePath
* @typedef {import('../types/analyzers').FindClassesAnalyzerOutputEntry} FindClassesAnalyzerOutputEntry * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('../types/analyzers').FindClassesConfig} FindClassesConfig * @typedef {import('../../../types/index.js').FindClassesAnalyzerResult} FindClassesAnalyzerResult
* @typedef {import('../../../types/index.js').FindClassesAnalyzerOutputFile} FindClassesAnalyzerOutputFile
* @typedef {import('../../../types/index.js').FindClassesAnalyzerEntry} FindClassesAnalyzerEntry
* @typedef {import('../../../types/index.js').FindClassesConfig} FindClassesConfig
*/ */
/** /**
* Finds import specifiers and sources * Finds import specifiers and sources
* @param {File} ast * @param {File} babelAst
* @param {string} fullCurrentFilePath the file being currently processed * @param {string} fullCurrentFilePath the file being currently processed
*/ */
async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) { async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath) {
// The transformed entry // The transformed entry
const classesFound = []; const classesFound = [];
/** /**
@ -82,18 +85,18 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
/** /**
* *
* @param {*} path * @param {NodePath} astPath
* @param {{isMixin?:boolean}} param1 * @param {{isMixin?:boolean}} opts
*/ */
async function traverseClass(path, { isMixin = false } = {}) { async function traverseClass(astPath, { isMixin = false } = {}) {
const classRes = {}; const classRes = {};
classRes.name = path.node.id && path.node.id.name; classRes.name = astPath.node.id && astPath.node.id.name;
classRes.isMixin = Boolean(isMixin); classRes.isMixin = Boolean(isMixin);
if (path.node.superClass) { if (astPath.node.superClass) {
const superClasses = []; const superClasses = [];
// Add all Identifier names // Add all Identifier names
let parent = path.node.superClass; let parent = astPath.node.superClass;
while (parent.type === 'CallExpression') { while (parent.type === 'CallExpression') {
superClasses.push({ name: parent.callee.name, isMixin: true }); superClasses.push({ name: parent.callee.name, isMixin: true });
// As long as we are a CallExpression, we will have a parent // As long as we are a CallExpression, we will have a parent
@ -103,15 +106,15 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
superClasses.push({ name: parent.name, isMixin: false }); superClasses.push({ name: parent.name, isMixin: false });
// For all found superclasses, track down their root location. // For all found superclasses, track down their root location.
// This will either result in a local, relative path in the project, // This will either result in a local, relative astPath in the project,
// or an external path like '@lion/overlays'. In the latter case, // or an external astPath like '@lion/overlays'. In the latter case,
// tracking down will halt and should be done when there is access to // tracking down will halt and should be done when there is access to
// the external repo... (similar to how 'match-imports' analyzer works) // the external repo... (similar to how 'match-imports' analyzer works)
for (const classObj of superClasses) { for (const classObj of superClasses) {
// Finds the file that holds the declaration of the import // Finds the file that holds the declaration of the import
classObj.rootFile = await trackDownIdentifierFromScope( classObj.rootFile = await trackDownIdentifierFromScope(
path, astPath,
classObj.name, classObj.name,
fullCurrentFilePath, fullCurrentFilePath,
projectPath, projectPath,
@ -127,18 +130,18 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
methods: [], methods: [],
}; };
path.traverse({ astPath.traverse({
ClassMethod(path) { ClassMethod(astPath) {
// if (isBlacklisted(path)) { // if (isBlacklisted(astPath)) {
// return; // return;
// } // }
if (isStaticProperties(path)) { if (isStaticProperties(astPath)) {
let hasFoundTopLvlObjExpr = false; let hasFoundTopLvlObjExpr = false;
path.traverse({ astPath.traverse({
ObjectExpression(path) { ObjectExpression(astPath) {
if (hasFoundTopLvlObjExpr) return; if (hasFoundTopLvlObjExpr) return;
hasFoundTopLvlObjExpr = true; hasFoundTopLvlObjExpr = true;
path.node.properties.forEach(objectProperty => { astPath.node.properties.forEach(objectProperty => {
if (!t.isProperty(objectProperty)) { if (!t.isProperty(objectProperty)) {
// we can also have a SpreadElement // we can also have a SpreadElement
return; return;
@ -156,19 +159,19 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
} }
const methodRes = {}; const methodRes = {};
const { name } = path.node.key; const { name } = astPath.node.key;
methodRes.name = name; methodRes.name = name;
methodRes.accessType = computeAccessType(name); methodRes.accessType = computeAccessType(name);
if (path.node.kind === 'set' || path.node.kind === 'get') { if (astPath.node.kind === 'set' || astPath.node.kind === 'get') {
if (path.node.static) { if (astPath.node.static) {
methodRes.static = true; methodRes.static = true;
} }
methodRes.kind = [...(methodRes.kind || []), path.node.kind]; methodRes.kind = [...(methodRes.kind || []), astPath.node.kind];
// Merge getter/setters into one // Merge getter/setters into one
const found = classRes.members.props.find(p => p.name === name); const found = classRes.members.props.find(p => p.name === name);
if (found) { if (found) {
found.kind = [...(found.kind || []), path.node.kind]; found.kind = [...(found.kind || []), astPath.node.kind];
} else { } else {
classRes.members.props.push(methodRes); classRes.members.props.push(methodRes);
} }
@ -182,17 +185,18 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
} }
const classesToTraverse = []; const classesToTraverse = [];
traverse(ast, {
ClassDeclaration(path) { babelTraverse.default(babelAst, {
classesToTraverse.push({ path, isMixin: false }); ClassDeclaration(astPath) {
classesToTraverse.push({ astPath, isMixin: false });
}, },
ClassExpression(path) { ClassExpression(astPath) {
classesToTraverse.push({ path, isMixin: true }); classesToTraverse.push({ astPath, isMixin: true });
}, },
}); });
for (const klass of classesToTraverse) { for (const klass of classesToTraverse) {
await traverseClass(klass.path, { isMixin: klass.isMixin }); await traverseClass(klass.astPath, { isMixin: klass.isMixin });
} }
return classesFound; return classesFound;
@ -218,24 +222,20 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
// }); // });
// } // }
class FindClassesAnalyzer extends Analyzer { export default class FindClassesAnalyzer extends Analyzer {
static get analyzerName() { /** @type {AnalyzerName} */
return 'find-classes'; static analyzerName = 'find-classes';
}
/** @type {'babel'|'swc-to-babel'} */
requiredAst = 'babel';
/** /**
* @desc Will find all public members (properties (incl. getter/setters)/functions) of a class and * Will find all public members (properties (incl. getter/setters)/functions) of a class and
* will make a distinction between private, public and protected methods * will make a distinction between private, public and protected methods
* @param {FindClassesConfig} customConfig * @param {Partial<FindClassesConfig>} customConfig
*/ */
async execute(customConfig = {}) { async execute(customConfig) {
/** @type {FindClassesConfig} */ const cfg = customConfig;
const cfg = {
gatherFilesConfig: {},
targetProjectPath: null,
metaConfig: null,
...customConfig,
};
/** /**
* Prepare * Prepare
@ -263,5 +263,3 @@ class FindClassesAnalyzer extends Analyzer {
return this._finalize(queryOutput, cfg); return this._finalize(queryOutput, cfg);
} }
} }
module.exports = FindClassesAnalyzer;

View file

@ -1,8 +1,14 @@
const pathLib = require('path'); import path from 'path';
const t = require('@babel/types'); import t from '@babel/types';
const { default: traverse } = require('@babel/traverse'); import babelTraverse from '@babel/traverse';
const { Analyzer } = require('../core/Analyzer.js'); import { Analyzer } from '../core/Analyzer.js';
const { trackDownIdentifierFromScope } = require('./helpers/track-down-identifier.js'); import { trackDownIdentifierFromScope } from './helpers/track-down-identifier.js';
/**
* @typedef {import('@babel/types').File} File
* @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('../../../types/index.js').FindCustomelementsConfig} FindCustomelementsConfig
*/
function cleanup(transformedEntry) { function cleanup(transformedEntry) {
transformedEntry.forEach(definitionObj => { transformedEntry.forEach(definitionObj => {
@ -15,11 +21,11 @@ function cleanup(transformedEntry) {
} }
async function trackdownRoot(transformedEntry, relativePath, projectPath) { async function trackdownRoot(transformedEntry, relativePath, projectPath) {
const fullCurrentFilePath = pathLib.resolve(projectPath, relativePath); const fullCurrentFilePath = path.resolve(projectPath, relativePath);
for (const definitionObj of transformedEntry) { for (const definitionObj of transformedEntry) {
const rootFile = await trackDownIdentifierFromScope( const rootFile = await trackDownIdentifierFromScope(
definitionObj.__tmp.path, definitionObj.__tmp.astPath,
definitionObj.constructorIdentifier, definitionObj.constructorIdentifier,
fullCurrentFilePath, fullCurrentFilePath,
projectPath, projectPath,
@ -31,19 +37,19 @@ async function trackdownRoot(transformedEntry, relativePath, projectPath) {
} }
/** /**
* @desc Finds import specifiers and sources * Finds import specifiers and sources
* @param {BabelAst} ast * @param {File} babelAst
*/ */
function findCustomElementsPerAstEntry(ast) { function findCustomElementsPerAstFile(babelAst) {
const definitions = []; const definitions = [];
traverse(ast, { babelTraverse.default(babelAst, {
CallExpression(path) { CallExpression(astPath) {
let found = false; let found = false;
// Doing it like this we detect 'customElements.define()', // Doing it like this we detect 'customElements.define()',
// but also 'window.customElements.define()' // but also 'window.customElements.define()'
path.traverse({ astPath.traverse({
MemberExpression(memberPath) { MemberExpression(memberPath) {
if (memberPath.parentPath !== path) { if (memberPath.parentPath !== astPath) {
return; return;
} }
const { node } = memberPath; const { node } = memberPath;
@ -64,29 +70,31 @@ function findCustomElementsPerAstEntry(ast) {
let tagName; let tagName;
let constructorIdentifier; let constructorIdentifier;
if (t.isLiteral(path.node.arguments[0])) { if (t.isLiteral(astPath.node.arguments[0])) {
tagName = path.node.arguments[0].value; tagName = astPath.node.arguments[0].value;
} else { } else {
// No Literal found. For now, we only mark them as '[variable]' // No Literal found. For now, we only mark them as '[variable]'
tagName = '[variable]'; tagName = '[variable]';
} }
if (path.node.arguments[1].type === 'Identifier') { if (astPath.node.arguments[1].type === 'Identifier') {
constructorIdentifier = path.node.arguments[1].name; constructorIdentifier = astPath.node.arguments[1].name;
} else { } else {
// We assume customElements.define('my-el', class extends HTMLElement {...}) // We assume customElements.define('my-el', class extends HTMLElement {...})
constructorIdentifier = '[inline]'; constructorIdentifier = '[inline]';
} }
definitions.push({ tagName, constructorIdentifier, __tmp: { path } }); definitions.push({ tagName, constructorIdentifier, __tmp: { astPath } });
} }
}, },
}); });
return definitions; return definitions;
} }
class FindCustomelementsAnalyzer extends Analyzer { export default class FindCustomelementsAnalyzer extends Analyzer {
static get analyzerName() { /** @type {AnalyzerName} */
return 'find-customelements'; static analyzerName = 'find-customelements';
}
/** @type {'babel'|'swc-to-babel'} */
requiredAst = 'swc-to-babel';
/** /**
* Finds export specifiers and sources * Finds export specifiers and sources
@ -110,9 +118,9 @@ class FindCustomelementsAnalyzer extends Analyzer {
* Traverse * Traverse
*/ */
const projectPath = cfg.targetProjectPath; const projectPath = cfg.targetProjectPath;
const queryOutput = await this._traverse(async (ast, { relativePath }) => { const queryOutput = await this._traverse(async (ast, context) => {
let transformedEntry = findCustomElementsPerAstEntry(ast); let transformedEntry = findCustomElementsPerAstFile(ast);
transformedEntry = await trackdownRoot(transformedEntry, relativePath, projectPath); transformedEntry = await trackdownRoot(transformedEntry, context.relativePath, projectPath);
transformedEntry = cleanup(transformedEntry); transformedEntry = cleanup(transformedEntry);
return { result: transformedEntry }; return { result: transformedEntry };
}); });
@ -123,5 +131,3 @@ class FindCustomelementsAnalyzer extends Analyzer {
return this._finalize(queryOutput, cfg); return this._finalize(queryOutput, cfg);
} }
} }
module.exports = FindCustomelementsAnalyzer;

View file

@ -1,29 +1,33 @@
/* eslint-disable no-shadow, no-param-reassign */ /* eslint-disable no-shadow, no-param-reassign */
const pathLib = require('path'); import pathLib from 'path';
const { default: traverse } = require('@babel/traverse'); import babelTraverse from '@babel/traverse';
const { Analyzer } = require('../core/Analyzer.js'); import { Analyzer } from '../core/Analyzer.js';
const { trackDownIdentifier } = require('./helpers/track-down-identifier.js'); import { trackDownIdentifier } from './helpers/track-down-identifier.js';
const { normalizeSourcePaths } = require('./helpers/normalize-source-paths.js'); import { normalizeSourcePaths } from './helpers/normalize-source-paths.js';
const { getReferencedDeclaration } = require('../utils/get-source-code-fragment-of-declaration.js'); import { getReferencedDeclaration } from '../utils/get-source-code-fragment-of-declaration.js';
import { LogService } from '../core/LogService.js';
const { LogService } = require('../core/LogService.js');
/** /**
* @typedef {import('@babel/types').File} File
* @typedef {import('@babel/types').Node} Node
* @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('../../../types/index.js').FindExportsAnalyzerResult} FindExportsAnalyzerResult
* @typedef {import('../../../types/index.js').FindExportsAnalyzerEntry} FindExportsAnalyzerEntry
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
* @typedef {import('./helpers/track-down-identifier.js').RootFile} RootFile * @typedef {import('./helpers/track-down-identifier.js').RootFile} RootFile
* @typedef {object} RootFileMapEntry * @typedef {object} RootFileMapEntry
* @typedef {string} currentFileSpecifier this is the local name in the file we track from * @typedef {string} currentFileSpecifier this is the local name in the file we track from
* @typedef {RootFile} rootFile contains file(filePath) and specifier * @typedef {RootFile} rootFile contains file(filePath) and specifier
* @typedef {RootFileMapEntry[]} RootFileMap * @typedef {RootFileMapEntry[]} RootFileMap
*
* @typedef {{ exportSpecifiers:string[]; localMap: object; source:string, __tmp: { path:string } }} FindExportsSpecifierObj * @typedef {{ exportSpecifiers:string[]; localMap: object; source:string, __tmp: { path:string } }} FindExportsSpecifierObj
*/ */
/** /**
* @param {FindExportsSpecifierObj[]} transformedEntry * @param {FindExportsSpecifierObj[]} transformedFile
*/ */
async function trackdownRoot(transformedEntry, relativePath, projectPath) { async function trackdownRoot(transformedFile, relativePath, projectPath) {
const fullCurrentFilePath = pathLib.resolve(projectPath, relativePath); const fullCurrentFilePath = pathLib.resolve(projectPath, relativePath);
for (const specObj of transformedEntry) { for (const specObj of transformedFile) {
/** @type {RootFileMap} */ /** @type {RootFileMap} */
const rootFileMap = []; const rootFileMap = [];
if (specObj.exportSpecifiers[0] === '[file]') { if (specObj.exportSpecifiers[0] === '[file]') {
@ -79,16 +83,16 @@ async function trackdownRoot(transformedEntry, relativePath, projectPath) {
} }
specObj.rootFileMap = rootFileMap; specObj.rootFileMap = rootFileMap;
} }
return transformedEntry; return transformedFile;
} }
function cleanup(transformedEntry) { function cleanup(transformedFile) {
transformedEntry.forEach(specObj => { transformedFile.forEach(specObj => {
if (specObj.__tmp) { if (specObj.__tmp) {
delete specObj.__tmp; delete specObj.__tmp;
} }
}); });
return transformedEntry; return transformedFile;
} }
/** /**
@ -110,7 +114,7 @@ function getExportSpecifiers(node) {
let specifier; let specifier;
if (s.exported) { if (s.exported) {
// { x as y } // { x as y }
specifier = s.exported.name; specifier = s.exported.name === 'default' ? '[default]' : s.exported.name;
} else { } else {
// { x } // { x }
specifier = s.local.name; specifier = s.local.name;
@ -142,22 +146,22 @@ const isImportingSpecifier = pathOrNode =>
/** /**
* Finds import specifiers and sources for a given ast result * Finds import specifiers and sources for a given ast result
* @param {File} ast * @param {File} babelAst
* @param {FindExportsConfig} config * @param {FindExportsConfig} config
*/ */
function findExportsPerAstEntry(ast, { skipFileImports }) { function findExportsPerAstFile(babelAst, { skipFileImports }) {
LogService.debug(`Analyzer "find-exports": started findExportsPerAstEntry method`); LogService.debug(`Analyzer "find-exports": started findExportsPerAstFile method`);
// Visit AST... // Visit AST...
/** @type {FindExportsSpecifierObj} */ /** @type {FindExportsSpecifierObj[]} */
const transformedEntry = []; const transformedFile = [];
// Unfortunately, we cannot have async functions in babel traverse. // Unfortunately, we cannot have async functions in babel traverse.
// Therefore, we store a temp reference to path that we use later for // Therefore, we store a temp reference to path that we use later for
// async post processing (tracking down original export Identifier) // async post processing (tracking down original export Identifier)
let globalScopeBindings; let globalScopeBindings;
traverse(ast, { babelTraverse.default(babelAst, {
Program: { Program: {
enter(babelPath) { enter(babelPath) {
const body = babelPath.get('body'); const body = babelPath.get('body');
@ -174,7 +178,7 @@ function findExportsPerAstEntry(ast, { skipFileImports }) {
if (path.node.assertions?.length) { if (path.node.assertions?.length) {
entry.assertionType = path.node.assertions[0].value?.value; entry.assertionType = path.node.assertions[0].value?.value;
} }
transformedEntry.push(entry); transformedFile.push(entry);
}, },
ExportDefaultDeclaration(defaultExportPath) { ExportDefaultDeclaration(defaultExportPath) {
const exportSpecifiers = ['[default]']; const exportSpecifiers = ['[default]'];
@ -190,26 +194,28 @@ function findExportsPerAstEntry(ast, { skipFileImports }) {
source = importOrDeclPath.parentPath.node.source.value; source = importOrDeclPath.parentPath.node.source.value;
} }
} }
transformedEntry.push({ exportSpecifiers, source, __tmp: { path: defaultExportPath } }); transformedFile.push({ exportSpecifiers, source, __tmp: { path: defaultExportPath } });
}, },
}); });
if (!skipFileImports) { if (!skipFileImports) {
// Always add an entry for just the file 'relativePath' // Always add an entry for just the file 'relativePath'
// (since this also can be imported directly from a search target project) // (since this also can be imported directly from a search target project)
transformedEntry.push({ transformedFile.push({
exportSpecifiers: ['[file]'], exportSpecifiers: ['[file]'],
// source: relativePath, // source: relativePath,
}); });
} }
return transformedEntry; return transformedFile;
} }
class FindExportsAnalyzer extends Analyzer { export default class FindExportsAnalyzer extends Analyzer {
static get analyzerName() { /** @type {AnalyzerName} */
return 'find-exports'; static analyzerName = 'find-exports';
}
/** @type {'babel'|'swc-to-babel'} */
requiredAst = 'swc-to-babel';
/** /**
* Finds export specifiers and sources * Finds export specifiers and sources
@ -243,13 +249,13 @@ class FindExportsAnalyzer extends Analyzer {
const projectPath = cfg.targetProjectPath; const projectPath = cfg.targetProjectPath;
const traverseEntryFn = async (ast, { relativePath }) => { const traverseEntryFn = async (ast, { relativePath }) => {
let transformedEntry = findExportsPerAstEntry(ast, cfg); let transformedFile = findExportsPerAstFile(ast, cfg);
transformedEntry = await normalizeSourcePaths(transformedEntry, relativePath, projectPath); transformedFile = await normalizeSourcePaths(transformedFile, relativePath, projectPath);
transformedEntry = await trackdownRoot(transformedEntry, relativePath, projectPath); transformedFile = await trackdownRoot(transformedFile, relativePath, projectPath);
transformedEntry = cleanup(transformedEntry); transformedFile = cleanup(transformedFile);
return { result: transformedEntry }; return { result: transformedFile };
}; };
const queryOutput = await this._traverse({ const queryOutput = await this._traverse({
@ -264,5 +270,3 @@ class FindExportsAnalyzer extends Analyzer {
return this._finalize(queryOutput, cfg); return this._finalize(queryOutput, cfg);
} }
} }
module.exports = FindExportsAnalyzer;

View file

@ -1,17 +1,18 @@
/* eslint-disable no-shadow, no-param-reassign */ /* eslint-disable no-shadow, no-param-reassign */
const { default: traverse } = require('@babel/traverse'); import babelTraverse from '@babel/traverse';
const { isRelativeSourcePath } = require('../utils/relative-source-path.js'); import { isRelativeSourcePath } from '../utils/relative-source-path.js';
const { normalizeSourcePaths } = require('./helpers/normalize-source-paths.js'); import { normalizeSourcePaths } from './helpers/normalize-source-paths.js';
const { Analyzer } = require('../core/Analyzer.js'); import { Analyzer } from '../core/Analyzer.js';
const { LogService } = require('../core/LogService.js'); import { LogService } from '../core/LogService.js';
/** /**
* @typedef {import('@babel/types').File} File * @typedef {import('@babel/types').File} File
* @typedef {import('@babel/types').Node} Node * * @typedef {import('@babel/types').Node} Node
* @typedef {import('../types/core').AnalyzerName} AnalyzerName * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult * @typedef {import('../../../types/index.js').AnalyzerConfig} AnalyzerConfig
* @typedef {import('../types/analyzers').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry * @typedef {import('../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult
* @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot * @typedef {import('../../../types/index.js').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
*/ */
/** /**
@ -36,7 +37,11 @@ const /** @type {AnalyzerConfig} */ options = {
*/ */
function getImportOrReexportsSpecifiers(node) { function getImportOrReexportsSpecifiers(node) {
return node.specifiers.map(s => { return node.specifiers.map(s => {
if (s.type === 'ImportDefaultSpecifier' || s.type === 'ExportDefaultSpecifier') { if (
s.type === 'ImportDefaultSpecifier' ||
s.type === 'ExportDefaultSpecifier' ||
(s.type === 'ExportSpecifier' && s.exported?.name === 'default')
) {
return '[default]'; return '[default]';
} }
if (s.type === 'ImportNamespaceSpecifier' || s.type === 'ExportNamespaceSpecifier') { if (s.type === 'ImportNamespaceSpecifier' || s.type === 'ExportNamespaceSpecifier') {
@ -54,31 +59,33 @@ function getImportOrReexportsSpecifiers(node) {
/** /**
* Finds import specifiers and sources * Finds import specifiers and sources
* @param {File} ast * @param {File} babelAst
*/ */
function findImportsPerAstEntry(ast) { function findImportsPerAstFile(babelAst, context) {
LogService.debug(`Analyzer "find-imports": started findImportsPerAstEntry method`); LogService.debug(`Analyzer "find-imports": started findImportsPerAstFile method`);
// https://github.com/babel/babel/blob/672a58660f0b15691c44582f1f3fdcdac0fa0d2f/packages/babel-core/src/transformation/index.ts#L110 // https://github.com/babel/babel/blob/672a58660f0b15691c44582f1f3fdcdac0fa0d2f/packages/babel-core/src/transformation/index.ts#L110
// Visit AST... // Visit AST...
/** @type {Partial<FindImportsAnalyzerEntry>[]} */ /** @type {Partial<FindImportsAnalyzerEntry>[]} */
const transformedEntry = []; const transformedFile = [];
traverse(ast, { babelTraverse.default(babelAst, {
ImportDeclaration(path) { ImportDeclaration(path) {
const importSpecifiers = getImportOrReexportsSpecifiers(path.node); const importSpecifiers = getImportOrReexportsSpecifiers(path.node);
if (!importSpecifiers.length) { if (!importSpecifiers.length) {
importSpecifiers.push('[file]'); // apparently, there was just a file import importSpecifiers.push('[file]'); // apparently, there was just a file import
} }
const source = path.node.source.value; const source = path.node.source.value;
const entry = { importSpecifiers, source }; const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source });
if (path.node.assertions?.length) { if (path.node.assertions?.length) {
entry.assertionType = path.node.assertions[0].value?.value; entry.assertionType = path.node.assertions[0].value?.value;
} }
transformedEntry.push(entry); transformedFile.push(entry);
}, },
// Dynamic imports // Dynamic imports
CallExpression(path) { CallExpression(path) {
if (path.node.callee && path.node.callee.type === 'Import') { if (path.node.callee?.type !== 'Import') {
return;
}
// TODO: check for specifiers catched via obj destructuring? // TODO: check for specifiers catched via obj destructuring?
// TODO: also check for ['file'] // TODO: also check for ['file']
const importSpecifiers = ['[default]']; const importSpecifiers = ['[default]'];
@ -87,8 +94,7 @@ function findImportsPerAstEntry(ast) {
// TODO: with advanced retrieval, we could possibly get the value // TODO: with advanced retrieval, we could possibly get the value
source = '[variable]'; source = '[variable]';
} }
transformedEntry.push({ importSpecifiers, source }); transformedFile.push({ importSpecifiers, source });
}
}, },
ExportNamedDeclaration(path) { ExportNamedDeclaration(path) {
if (!path.node.source) { if (!path.node.source) {
@ -96,11 +102,11 @@ function findImportsPerAstEntry(ast) {
} }
const importSpecifiers = getImportOrReexportsSpecifiers(path.node); const importSpecifiers = getImportOrReexportsSpecifiers(path.node);
const source = path.node.source.value; const source = path.node.source.value;
const entry = { importSpecifiers, source }; const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source });
if (path.node.assertions?.length) { if (path.node.assertions?.length) {
entry.assertionType = path.node.assertions[0].value?.value; entry.assertionType = path.node.assertions[0].value?.value;
} }
transformedEntry.push(entry); transformedFile.push(entry);
}, },
// ExportAllDeclaration(path) { // ExportAllDeclaration(path) {
// if (!path.node.source) { // if (!path.node.source) {
@ -108,18 +114,19 @@ function findImportsPerAstEntry(ast) {
// } // }
// const importSpecifiers = ['[*]']; // const importSpecifiers = ['[*]'];
// const source = path.node.source.value; // const source = path.node.source.value;
// transformedEntry.push({ importSpecifiers, source }); // transformedFile.push({ importSpecifiers, source });
// }, // },
}); });
return transformedEntry; return transformedFile;
} }
class FindImportsAnalyzer extends Analyzer { export default class FindImportsAnalyzer extends Analyzer {
/** @type {AnalyzerName} */ /** @type {AnalyzerName} */
static get analyzerName() { static analyzerName = 'find-imports';
return 'find-imports';
} /** @type {'babel'|'swc-to-babel'} */
requiredAst = 'swc-to-babel';
/** /**
* Finds import specifiers and sources * Finds import specifiers and sources
@ -143,28 +150,28 @@ class FindImportsAnalyzer extends Analyzer {
/** /**
* Prepare * Prepare
*/ */
const analyzerResult = this._prepare(cfg); const cachedAnalyzerResult = this._prepare(cfg);
if (analyzerResult) { if (cachedAnalyzerResult) {
return analyzerResult; return cachedAnalyzerResult;
} }
/** /**
* Traverse * Traverse
*/ */
const queryOutput = await this._traverse(async (ast, { relativePath }) => { const queryOutput = await this._traverse(async (ast, context) => {
let transformedEntry = findImportsPerAstEntry(ast); let transformedFile = findImportsPerAstFile(ast, context);
// Post processing based on configuration... // Post processing based on configuration...
transformedEntry = await normalizeSourcePaths( transformedFile = await normalizeSourcePaths(
transformedEntry, transformedFile,
relativePath, context.relativePath,
cfg.targetProjectPath, cfg.targetProjectPath,
); );
if (!cfg.keepInternalSources) { if (!cfg.keepInternalSources) {
transformedEntry = options.onlyExternalSources(transformedEntry); transformedFile = options.onlyExternalSources(transformedFile);
} }
return { result: transformedEntry }; return { result: transformedFile };
}); });
// if (cfg.sortBySpecifier) { // if (cfg.sortBySpecifier) {
@ -180,5 +187,3 @@ class FindImportsAnalyzer extends Analyzer {
return this._finalize(queryOutput, cfg); return this._finalize(queryOutput, cfg);
} }
} }
module.exports = FindImportsAnalyzer;

View file

@ -1,12 +1,12 @@
const { isRelativeSourcePath } = require('../../utils/relative-source-path.js'); import { isRelativeSourcePath } from '../../utils/relative-source-path.js';
const { LogService } = require('../../core/LogService.js'); import { LogService } from '../../core/LogService.js';
const { resolveImportPath } = require('../../utils/resolve-import-path.js'); import { resolveImportPath } from '../../utils/resolve-import-path.js';
const { toPosixPath } = require('../../utils/to-posix-path.js'); import { toPosixPath } from '../../utils/to-posix-path.js';
/** /**
* @typedef {import('../../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot * @typedef {import('../../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
* @typedef {import('../../types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../../types/core').SpecifierSource} SpecifierSource * @typedef {import('../../../../types/index.js').SpecifierSource} SpecifierSource
*/ */
/** /**
@ -23,7 +23,7 @@ const { toPosixPath } = require('../../utils/to-posix-path.js');
* @param {PathFromSystemRoot} config.importeeProjectPath '/path/to/reference/project' * @param {PathFromSystemRoot} config.importeeProjectPath '/path/to/reference/project'
* @returns {Promise<PathRelativeFromProjectRoot|null>} './foo.js' * @returns {Promise<PathRelativeFromProjectRoot|null>} './foo.js'
*/ */
async function fromImportToExportPerspective({ importee, importer, importeeProjectPath }) { export async function fromImportToExportPerspective({ importee, importer, importeeProjectPath }) {
if (isRelativeSourcePath(importee)) { if (isRelativeSourcePath(importee)) {
LogService.warn('[fromImportToExportPerspective] Please only provide external import paths'); LogService.warn('[fromImportToExportPerspective] Please only provide external import paths');
return null; return null;
@ -38,5 +38,3 @@ async function fromImportToExportPerspective({ importee, importer, importeeProje
absolutePath.replace(new RegExp(`^${toPosixPath(importeeProjectPath)}/?(.*)$`), './$1') absolutePath.replace(new RegExp(`^${toPosixPath(importeeProjectPath)}/?(.*)$`), './$1')
); );
} }
module.exports = { fromImportToExportPerspective };

View file

@ -1,17 +1,16 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
const pathLib = require('path'); import pathLib from 'path';
const { isRelativeSourcePath } = require('../../utils/relative-source-path.js'); import { isRelativeSourcePath } from '../../utils/relative-source-path.js';
const { resolveImportPath } = require('../../utils/resolve-import-path.js'); import { resolveImportPath } from '../../utils/resolve-import-path.js';
const { toPosixPath } = require('../../utils/to-posix-path.js'); import { toPosixPath } from '../../utils/to-posix-path.js';
/** /**
* @typedef {import('../../types/core').PathRelative} PathRelative * @typedef {import('../../../../types/index.js').PathRelative} PathRelative
* @typedef {import('../../types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../../types/core').QueryOutput} QueryOutput * @typedef {import('../../../../types/index.js').QueryOutput} QueryOutput
*/ */
/** /**
*
* @param {PathFromSystemRoot} currentDirPath * @param {PathFromSystemRoot} currentDirPath
* @param {PathFromSystemRoot} resolvedPath * @param {PathFromSystemRoot} resolvedPath
* @returns {PathRelative} * @returns {PathRelative}
@ -38,7 +37,7 @@ function toLocalPath(currentDirPath, resolvedPath) {
* @param {string} relativePath * @param {string} relativePath
* @param {string} rootPath * @param {string} rootPath
*/ */
async function normalizeSourcePaths(queryOutput, relativePath, rootPath = process.cwd()) { export async function normalizeSourcePaths(queryOutput, relativePath, rootPath = process.cwd()) {
const currentFilePath = /** @type {PathFromSystemRoot} */ ( const currentFilePath = /** @type {PathFromSystemRoot} */ (
pathLib.resolve(rootPath, relativePath) pathLib.resolve(rootPath, relativePath)
); );
@ -65,5 +64,3 @@ async function normalizeSourcePaths(queryOutput, relativePath, rootPath = proces
} }
return normalizedQueryOutput; return normalizedQueryOutput;
} }
module.exports = { normalizeSourcePaths };

View file

@ -1,21 +1,19 @@
const fs = require('fs'); import fs from 'fs';
const pathLib = require('path'); import pathLib from 'path';
const { default: traverse } = require('@babel/traverse'); import babelTraverse from '@babel/traverse';
const { import { isRelativeSourcePath, toRelativeSourcePath } from '../../utils/relative-source-path.js';
isRelativeSourcePath, import { InputDataService } from '../../core/InputDataService.js';
toRelativeSourcePath, import { resolveImportPath } from '../../utils/resolve-import-path.js';
} = require('../../utils/relative-source-path.js'); import { AstService } from '../../core/AstService.js';
const { InputDataService } = require('../../core/InputDataService.js'); import { LogService } from '../../core/LogService.js';
const { resolveImportPath } = require('../../utils/resolve-import-path.js'); import { memoize } from '../../utils/memoize.js';
const { AstService } = require('../../core/AstService.js');
const { LogService } = require('../../core/LogService.js');
const { memoize } = require('../../utils/memoize.js');
/** /**
* @typedef {import('../../types/core').RootFile} RootFile * @typedef {import('../../../../types/index.js').RootFile} RootFile
* @typedef {import('../../types/core').SpecifierSource} SpecifierSource * @typedef {import('../../../../types/index.js').SpecifierSource} SpecifierSource
* @typedef {import('../../types/core').IdentifierName} IdentifierName * @typedef {import('../../../../types/index.js').IdentifierName} IdentifierName
* @typedef {import('../../types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('@babel/traverse').NodePath} NodePath
*/ */
/** /**
@ -42,7 +40,7 @@ function isExternalProject(source, projectName) {
* Other than with import, no binding is created for MyClass by Babel(?) * Other than with import, no binding is created for MyClass by Babel(?)
* This means 'path.scope.getBinding('MyClass')' returns undefined * This means 'path.scope.getBinding('MyClass')' returns undefined
* and we have to find a different way to retrieve this value. * and we have to find a different way to retrieve this value.
* @param {object} astPath Babel ast traversal path * @param {NodePath} astPath Babel ast traversal path
* @param {IdentifierName} identifierName the name that should be tracked (and that exists inside scope of astPath) * @param {IdentifierName} identifierName the name that should be tracked (and that exists inside scope of astPath)
*/ */
function getBindingAndSourceReexports(astPath, identifierName) { function getBindingAndSourceReexports(astPath, identifierName) {
@ -60,10 +58,12 @@ function getBindingAndSourceReexports(astPath, identifierName) {
ExportSpecifier(path) { ExportSpecifier(path) {
// eslint-disable-next-line arrow-body-style // eslint-disable-next-line arrow-body-style
const found = const found =
// @ts-expect-error
path.node.exported.name === identifierName || path.node.local.name === identifierName; path.node.exported.name === identifierName || path.node.local.name === identifierName;
if (found) { if (found) {
bindingPath = path; bindingPath = path;
bindingType = 'ExportSpecifier'; bindingType = 'ExportSpecifier';
// @ts-expect-error
source = path.parentPath.node.source ? path.parentPath.node.source.value : '[current]'; source = path.parentPath.node.source ? path.parentPath.node.source.value : '[current]';
path.stop(); path.stop();
} }
@ -78,21 +78,22 @@ function getBindingAndSourceReexports(astPath, identifierName) {
* We might be an import that was locally renamed. * We might be an import that was locally renamed.
* Since we are traversing, we are interested in the imported name. Or in case of a re-export, * Since we are traversing, we are interested in the imported name. Or in case of a re-export,
* the local name. * the local name.
* @param {object} astPath Babel ast traversal path * @param {NodePath} astPath Babel ast traversal path
* @param {string} identifierName the name that should be tracked (and that exists inside scope of astPath) * @param {string} identifierName the name that should be tracked (and that exists inside scope of astPath)
* @returns {{ source:string, importedIdentifierName:string }} * @returns {{ source:string, importedIdentifierName:string }}
*/ */
function getImportSourceFromAst(astPath, identifierName) { export function getImportSourceFromAst(astPath, identifierName) {
let source; let source;
let importedIdentifierName; let importedIdentifierName;
const binding = astPath.scope.getBinding(identifierName); const binding = astPath.scope.getBinding(identifierName);
let bindingType = binding && binding.path.type; let bindingType = binding?.path.type;
let bindingPath = binding && binding.path; let bindingPath = binding?.path;
const matchingTypes = ['ImportSpecifier', 'ImportDefaultSpecifier', 'ExportSpecifier']; const matchingTypes = ['ImportSpecifier', 'ImportDefaultSpecifier', 'ExportSpecifier'];
if (binding && matchingTypes.includes(bindingType)) { if (bindingType && matchingTypes.includes(bindingType)) {
source = binding.path.parentPath.node.source.value; // @ts-expect-error
source = binding?.path?.parentPath?.node?.source?.value;
} else { } else {
// no binding // no binding
[source, bindingType, bindingPath] = getBindingAndSourceReexports(astPath, identifierName); [source, bindingType, bindingPath] = getBindingAndSourceReexports(astPath, identifierName);
@ -102,15 +103,19 @@ function getImportSourceFromAst(astPath, identifierName) {
if (shouldLookForDefaultExport) { if (shouldLookForDefaultExport) {
importedIdentifierName = '[default]'; importedIdentifierName = '[default]';
} else if (source) { } else if (source) {
// @ts-expect-error
const { node } = bindingPath; const { node } = bindingPath;
importedIdentifierName = (node.imported && node.imported.name) || node.local.name; importedIdentifierName = (node.imported && node.imported.name) || node.local.name;
} }
return { source, importedIdentifierName }; return { source, importedIdentifierName };
} }
/** @type {(source:SpecifierSource,identifierName:IdentifierName,currentFilePath:PathFromSystemRoot,rootPath:PathFromSystemRoot, depth?:number) => Promise<RootFile>} */
let trackDownIdentifier;
/** /**
* @typedef {(source:SpecifierSource,identifierName:IdentifierName,currentFilePath:PathFromSystemRoot,rootPath:PathFromSystemRoot,projectName?: string,depth?:number) => Promise<RootFile>} TrackDownIdentifierFn
*/
/**
* Follows the full path of an Identifier until its declaration ('root file') is found.
* @example * @example
*```js *```js
* // 1. Starting point * // 1. Starting point
@ -126,14 +131,19 @@ let trackDownIdentifier;
* export class RefComp extends LitElement {...} * export class RefComp extends LitElement {...}
*``` *```
* *
* @param {SpecifierSource} source an importSpecifier source, like 'ref-proj' or '../file' * -param {SpecifierSource} source an importSpecifier source, like 'ref-proj' or '../file'
* @param {IdentifierName} identifierName imported reference/Identifier name, like 'MyComp' * -param {IdentifierName} identifierName imported reference/Identifier name, like 'MyComp'
* @param {PathFromSystemRoot} currentFilePath file path, like '/path/to/target-proj/my-comp-import.js' * -param {PathFromSystemRoot} currentFilePath file path, like '/path/to/target-proj/my-comp-import.js'
* @param {PathFromSystemRoot} rootPath dir path, like '/path/to/target-proj' * -param {PathFromSystemRoot} rootPath dir path, like '/path/to/target-proj'
* @param {string} [projectName] like 'target-proj' or '@lion/input' * -param {string} [projectName] like 'target-proj' or '@lion/input'
* @returns {Promise<RootFile>} file: path of file containing the binding (exported declaration), * -returns {Promise<RootFile>} file: path of file containing the binding (exported declaration),
* like '/path/to/ref-proj/src/RefComp.js' * like '/path/to/ref-proj/src/RefComp.js'
*/ */
/** @type {TrackDownIdentifierFn} */
// eslint-disable-next-line import/no-mutable-exports
export let trackDownIdentifier;
/** @type {TrackDownIdentifierFn} */
async function trackDownIdentifierFn( async function trackDownIdentifierFn(
source, source,
identifierName, identifierName,
@ -150,7 +160,7 @@ async function trackDownIdentifierFn(
projectName = InputDataService.getPackageJson(rootPath)?.name; projectName = InputDataService.getPackageJson(rootPath)?.name;
} }
if (isExternalProject(source, projectName)) { if (projectName && isExternalProject(source, projectName)) {
// So, it is an external ref like '@lion/core' or '@open-wc/scoped-elements/index.js' // So, it is an external ref like '@lion/core' or '@open-wc/scoped-elements/index.js'
// At this moment in time, we don't know if we have file system access to this particular // At this moment in time, we don't know if we have file system access to this particular
// project. Therefore, we limit ourselves to tracking down local references. // project. Therefore, we limit ourselves to tracking down local references.
@ -162,9 +172,6 @@ async function trackDownIdentifierFn(
return result; return result;
} }
/**
* @type {PathFromSystemRoot}
*/
const resolvedSourcePath = await resolveImportPath(source, currentFilePath); const resolvedSourcePath = await resolveImportPath(source, currentFilePath);
LogService.debug(`[trackDownIdentifier] ${resolvedSourcePath}`); LogService.debug(`[trackDownIdentifier] ${resolvedSourcePath}`);
@ -177,14 +184,15 @@ async function trackDownIdentifierFn(
}; };
} }
const code = fs.readFileSync(resolvedSourcePath, 'utf8'); const code = fs.readFileSync(resolvedSourcePath, 'utf8');
const ast = AstService.getAst(code, 'babel', { filePath: resolvedSourcePath }); const babelAst = AstService.getAst(code, 'swc-to-babel', { filePath: resolvedSourcePath });
const shouldLookForDefaultExport = identifierName === '[default]'; const shouldLookForDefaultExport = identifierName === '[default]';
let reexportMatch = false; // named specifier declaration let reexportMatch = false; // named specifier declaration
let exportMatch; let exportMatch;
let pendingTrackDownPromise; let pendingTrackDownPromise;
traverse(ast, { babelTraverse.default(babelAst, {
ExportDefaultDeclaration(path) { ExportDefaultDeclaration(path) {
if (!shouldLookForDefaultExport) { if (!shouldLookForDefaultExport) {
return; return;
@ -288,10 +296,10 @@ async function trackDownIdentifierFn(
trackDownIdentifier = memoize(trackDownIdentifierFn); trackDownIdentifier = memoize(trackDownIdentifierFn);
/** /**
* @param {BabelPath} astPath * @param {NodePath} astPath
* @param {string} identifierNameInScope * @param {string} identifierNameInScope
* @param {string} fullCurrentFilePath * @param {PathFromSystemRoot} fullCurrentFilePath
* @param {string} projectPath * @param {PathFromSystemRoot} projectPath
* @param {string} [projectName] * @param {string} [projectName]
*/ */
async function trackDownIdentifierFromScopeFn( async function trackDownIdentifierFromScopeFn(
@ -320,10 +328,4 @@ async function trackDownIdentifierFromScopeFn(
return rootFile; return rootFile;
} }
const trackDownIdentifierFromScope = memoize(trackDownIdentifierFromScopeFn); export const trackDownIdentifierFromScope = memoize(trackDownIdentifierFromScopeFn);
module.exports = {
trackDownIdentifier,
getImportSourceFromAst,
trackDownIdentifierFromScope,
};

View file

@ -1,5 +1,5 @@
/** /**
* @typedef {import('../../types/analyzers').FindExportsAnalyzerResult} FindExportsAnalyzerResult * @typedef {import('../../../../types/index.js').FindExportsAnalyzerResult} FindExportsAnalyzerResult
*/ */
/** /**
@ -35,7 +35,7 @@
* *
* @param {FindExportsAnalyzerResult} exportsAnalyzerResult * @param {FindExportsAnalyzerResult} exportsAnalyzerResult
*/ */
function transformIntoIterableFindExportsOutput(exportsAnalyzerResult) { export function transformIntoIterableFindExportsOutput(exportsAnalyzerResult) {
/** @type {IterableFindExportsAnalyzerEntry[]} */ /** @type {IterableFindExportsAnalyzerEntry[]} */
const iterableEntries = []; const iterableEntries = [];
@ -62,6 +62,3 @@ function transformIntoIterableFindExportsOutput(exportsAnalyzerResult) {
} }
return iterableEntries; return iterableEntries;
} }
module.exports = {
transformIntoIterableFindExportsOutput,
};

View file

@ -1,5 +1,5 @@
/** /**
* @typedef {import('../../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult * @typedef {import('../../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult
*/ */
/** /**
@ -32,7 +32,7 @@
* *
* @param {FindImportsAnalyzerResult} importsAnalyzerResult * @param {FindImportsAnalyzerResult} importsAnalyzerResult
*/ */
function transformIntoIterableFindImportsOutput(importsAnalyzerResult) { export function transformIntoIterableFindImportsOutput(importsAnalyzerResult) {
/** @type {IterableFindImportsAnalyzerEntry[]} */ /** @type {IterableFindImportsAnalyzerEntry[]} */
const iterableEntries = []; const iterableEntries = [];
@ -56,7 +56,3 @@ function transformIntoIterableFindImportsOutput(importsAnalyzerResult) {
} }
return iterableEntries; return iterableEntries;
} }
module.exports = {
transformIntoIterableFindImportsOutput,
};

View file

@ -1,23 +1,10 @@
// A base class for writing Analyzers // A base class for writing Analyzers
const { Analyzer } = require('../core/Analyzer.js'); export { Analyzer } from '../core/Analyzer.js';
// Expose analyzers that are requested to be run in external contexts // Expose analyzers that are requested to be run in external contexts
const FindExportsAnalyzer = require('./find-exports.js'); export { default as FindExportsAnalyzer } from './find-exports.js';
const FindImportsAnalyzer = require('./find-imports.js'); export { default as FindImportsAnalyzer } from './find-imports.js';
const MatchImportsAnalyzer = require('./match-paths.js'); export { default as MatchImportsAnalyzer } from './match-paths.js';
const { export { transformIntoIterableFindImportsOutput } from './helpers/transform-into-iterable-find-imports-output.js';
transformIntoIterableFindImportsOutput, export { transformIntoIterableFindExportsOutput } from './helpers/transform-into-iterable-find-exports-output.js';
} = require('./helpers/transform-into-iterable-find-imports-output.js');
const {
transformIntoIterableFindExportsOutput,
} = require('./helpers/transform-into-iterable-find-exports-output.js');
module.exports = {
Analyzer,
FindExportsAnalyzer,
FindImportsAnalyzer,
MatchImportsAnalyzer,
transformIntoIterableFindImportsOutput,
transformIntoIterableFindExportsOutput,
};

View file

@ -1,28 +1,24 @@
/* eslint-disable no-continue */ /* eslint-disable no-continue */
const pathLib = require('path'); import pathLib from 'path';
/* eslint-disable no-shadow, no-param-reassign */ /* eslint-disable no-shadow, no-param-reassign */
const FindImportsAnalyzer = require('./find-imports.js'); import FindImportsAnalyzer from './find-imports.js';
const FindExportsAnalyzer = require('./find-exports.js'); import FindExportsAnalyzer from './find-exports.js';
const { Analyzer } = require('../core/Analyzer.js'); import { Analyzer } from '../core/Analyzer.js';
const { fromImportToExportPerspective } = require('./helpers/from-import-to-export-perspective.js'); import { fromImportToExportPerspective } from './helpers/from-import-to-export-perspective.js';
const { import { transformIntoIterableFindExportsOutput } from './helpers/transform-into-iterable-find-exports-output.js';
transformIntoIterableFindExportsOutput, import { transformIntoIterableFindImportsOutput } from './helpers/transform-into-iterable-find-imports-output.js';
} = require('./helpers/transform-into-iterable-find-exports-output.js');
const {
transformIntoIterableFindImportsOutput,
} = require('./helpers/transform-into-iterable-find-imports-output.js');
/** /**
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult * @typedef {import('../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult
* @typedef {import('../types/analyzers').FindExportsAnalyzerResult} FindExportsAnalyzerResult * @typedef {import('../../../types/index.js').FindExportsAnalyzerResult} FindExportsAnalyzerResult
* @typedef {import('../types/analyzers').IterableFindExportsAnalyzerEntry} IterableFindExportsAnalyzerEntry * @typedef {import('../../../types/index.js').IterableFindExportsAnalyzerEntry} IterableFindExportsAnalyzerEntry
* @typedef {import('../types/analyzers').IterableFindImportsAnalyzerEntry} IterableFindImportsAnalyzerEntry * @typedef {import('../../../types/index.js').IterableFindImportsAnalyzerEntry} IterableFindImportsAnalyzerEntry
* @typedef {import('../types/analyzers').ConciseMatchImportsAnalyzerResult} ConciseMatchImportsAnalyzerResult * @typedef {import('../../../types/index.js').ConciseMatchImportsAnalyzerResult} ConciseMatchImportsAnalyzerResult
* @typedef {import('../types/analyzers').MatchImportsConfig} MatchImportsConfig * @typedef {import('../../../types/index.js').MatchImportsConfig} MatchImportsConfig
* @typedef {import('../types/analyzers').MatchImportsAnalyzerResult} MatchImportsAnalyzerResult * @typedef {import('../../../types/index.js').MatchImportsAnalyzerResult} MatchImportsAnalyzerResult
* @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot * @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
* @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../types/core').AnalyzerName} AnalyzerName * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
*/ */
/** /**
@ -77,6 +73,7 @@ async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerRes
// TODO: What if this info is retrieved from cached importProject/target project? // TODO: What if this info is retrieved from cached importProject/target project?
const importProjectPath = cfg.targetProjectPath; const importProjectPath = cfg.targetProjectPath;
// TODO: make find-import / export automatically output these, to improve perf...
const iterableFindExportsOutput = transformIntoIterableFindExportsOutput(exportsAnalyzerResult); const iterableFindExportsOutput = transformIntoIterableFindExportsOutput(exportsAnalyzerResult);
const iterableFindImportsOutput = transformIntoIterableFindImportsOutput(importsAnalyzerResult); const iterableFindImportsOutput = transformIntoIterableFindImportsOutput(importsAnalyzerResult);
@ -107,7 +104,7 @@ async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerRes
/** /**
* 2. Are we from the same source? * 2. Are we from the same source?
* A.k.a. is source required by target the same as the one found in target. * A.k.a. is source required by target the same as the one found in target.
* (we know the specifier name is tha same, now we need to check the file as well.) * (we know the specifier name is the same, now we need to check the file as well.)
* *
* Example: * Example:
* exportFile './foo.js' * exportFile './foo.js'
@ -152,20 +149,16 @@ async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerRes
} }
const importProject = importsAnalyzerResult.analyzerMeta.targetProject.name; const importProject = importsAnalyzerResult.analyzerMeta.targetProject.name;
return /** @type {AnalyzerQueryResult} */ createCompatibleMatchImportsResult( return /** @type {AnalyzerQueryResult} */ (
conciseResultsArray, createCompatibleMatchImportsResult(conciseResultsArray, importProject)
importProject,
); );
} }
class MatchImportsAnalyzer extends Analyzer { export default class MatchImportsAnalyzer extends Analyzer {
static get analyzerName() { /** @type {AnalyzerName} */
return 'match-imports'; static analyzerName = 'match-imports';
}
static get requiresReference() { static requiresReference = true;
return true;
}
/** /**
* Based on ExportsAnalyzerResult of reference project(s) (for instance lion-based-ui) * Based on ExportsAnalyzerResult of reference project(s) (for instance lion-based-ui)
@ -197,10 +190,9 @@ class MatchImportsAnalyzer extends Analyzer {
/** /**
* Prepare * Prepare
*/ */
const analyzerResult = this._prepare(cfg); const cachedAnalyzerResult = this._prepare(cfg);
if (cachedAnalyzerResult) {
if (analyzerResult) { return cachedAnalyzerResult;
return analyzerResult;
} }
/** /**
@ -240,5 +232,3 @@ class MatchImportsAnalyzer extends Analyzer {
return this._finalize(queryOutput, cfg); return this._finalize(queryOutput, cfg);
} }
} }
module.exports = MatchImportsAnalyzer;

View file

@ -1,15 +1,18 @@
/* eslint-disable no-shadow, no-param-reassign */ /* eslint-disable no-shadow, no-param-reassign */
const MatchSubclassesAnalyzer = require('./match-subclasses.js'); import MatchSubclassesAnalyzer from './match-subclasses.js';
const FindExportsAnalyzer = require('./find-exports.js'); import FindExportsAnalyzer from './find-exports.js';
const FindCustomelementsAnalyzer = require('./find-customelements.js'); import FindCustomelementsAnalyzer from './find-customelements.js';
const { Analyzer } = require('../core/Analyzer.js'); import { Analyzer } from '../core/Analyzer.js';
/** @typedef {import('../types/core').FindExportsAnalyzerResult} FindExportsAnalyzerResult */ /**
/** @typedef {import('../types/core').FindCustomelementsAnalyzerResult} FindCustomelementsAnalyzerResult */ * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
/** @typedef {import('../types/core').MatchSubclassesAnalyzerResult} MatchSubclassesAnalyzerResult */ * @typedef {import('../../../types/index.js').FindExportsAnalyzerResult} FindExportsAnalyzerResult
/** @typedef {import('../types/core').FindImportsAnalyzerResult} FindImportsAnalyzerResult */ * @typedef {import('../../../types/index.js').FindCustomelementsAnalyzerResult} FindCustomelementsAnalyzerResult
/** @typedef {import('../types/core').MatchedExportSpecifier} MatchedExportSpecifier */ * @typedef {import('../../../types/index.js').MatchSubclassesAnalyzerResult} MatchSubclassesAnalyzerResult
/** @typedef {import('../types/core').RootFile} RootFile */ * @typedef {import('../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult
* @typedef {import('../../../types/index.js').MatchedExportSpecifier} MatchedExportSpecifier
* @typedef {import('../../../types/index.js').RootFile} RootFile
*/
/** /**
* For prefix `{ from: 'lion', to: 'wolf' }` * For prefix `{ from: 'lion', to: 'wolf' }`
@ -361,14 +364,11 @@ function matchPathsPostprocess(
* ... * ...
* ] * ]
*/ */
class MatchPathsAnalyzer extends Analyzer { export default class MatchPathsAnalyzer extends Analyzer {
static get analyzerName() { /** @type {AnalyzerName} */
return 'match-paths'; static analyzerName = 'match-paths';
}
static get requiresReference() { static requiresReference = true;
return true;
}
/** /**
* @param {MatchClasspathsConfig} customConfig * @param {MatchClasspathsConfig} customConfig
@ -513,5 +513,3 @@ class MatchPathsAnalyzer extends Analyzer {
return this._finalize(queryOutput, cfg); return this._finalize(queryOutput, cfg);
} }
} }
module.exports = MatchPathsAnalyzer;

View file

@ -1,21 +1,21 @@
/* eslint-disable no-continue */ /* eslint-disable no-continue */
const pathLib = require('path'); import pathLib from 'path';
/* eslint-disable no-shadow, no-param-reassign */ /* eslint-disable no-shadow, no-param-reassign */
const FindClassesAnalyzer = require('./find-classes.js'); import FindClassesAnalyzer from './find-classes.js';
const FindExportsAnalyzer = require('./find-exports.js'); import FindExportsAnalyzer from './find-exports.js';
const { Analyzer } = require('../core/Analyzer.js'); import { Analyzer } from '../core/Analyzer.js';
const { fromImportToExportPerspective } = require('./helpers/from-import-to-export-perspective.js'); import { fromImportToExportPerspective } from './helpers/from-import-to-export-perspective.js';
/** /**
* @typedef {import('../types/analyzers').FindClassesAnalyzerResult} FindClassesAnalyzerResult * @typedef {import('../../../types/index.js').FindClassesAnalyzerResult} FindClassesAnalyzerResult
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult * @typedef {import('../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult
* @typedef {import('../types/analyzers').FindExportsAnalyzerResult} FindExportsAnalyzerResult * @typedef {import('../../../types/index.js').FindExportsAnalyzerResult} FindExportsAnalyzerResult
* @typedef {import('../types/analyzers').IterableFindExportsAnalyzerEntry} IterableFindExportsAnalyzerEntry * @typedef {import('../../../types/index.js').IterableFindExportsAnalyzerEntry} IterableFindExportsAnalyzerEntry
* @typedef {import('../types/analyzers').IterableFindImportsAnalyzerEntry} IterableFindImportsAnalyzerEntry * @typedef {import('../../../types/index.js').IterableFindImportsAnalyzerEntry} IterableFindImportsAnalyzerEntry
* @typedef {import('../types/analyzers').ConciseMatchImportsAnalyzerResult} ConciseMatchImportsAnalyzerResult * @typedef {import('../../../types/index.js').ConciseMatchImportsAnalyzerResult} ConciseMatchImportsAnalyzerResult
* @typedef {import('../types/analyzers').MatchImportsConfig} MatchImportsConfig * @typedef {import('../../../types/index.js').MatchImportsConfig} MatchImportsConfig
* @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot * @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
* @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
*/ */
function getMemberOverrides( function getMemberOverrides(
@ -87,7 +87,7 @@ async function matchSubclassesPostprocess(
/** /**
* Step 1: a 'flat' data structure * Step 1: a 'flat' data structure
* @desc Create a key value storage map for exports/class matches * Create a key value storage map for exports/class matches
* - key: `${exportSpecifier}::${normalizedSource}::${project}` from reference project * - key: `${exportSpecifier}::${normalizedSource}::${project}` from reference project
* - value: an array of import file matches like `${targetProject}::${normalizedSource}::${className}` * - value: an array of import file matches like `${targetProject}::${normalizedSource}::${className}`
* @example * @example
@ -199,7 +199,7 @@ async function matchSubclassesPostprocess(
/** /**
* Step 2: a rich data structure * Step 2: a rich data structure
* @desc Transform resultObj from step 1 into an array of objects * Transform resultObj from step 1 into an array of objects
* @example * @example
* [{ * [{
* exportSpecifier: { * exportSpecifier: {
@ -275,7 +275,7 @@ async function matchSubclassesPostprocess(
// return aResult; // return aResult;
// } // }
class MatchSubclassesAnalyzer extends Analyzer { export default class MatchSubclassesAnalyzer extends Analyzer {
static get analyzerName() { static get analyzerName() {
return 'match-subclasses'; return 'match-subclasses';
} }
@ -285,7 +285,7 @@ class MatchSubclassesAnalyzer extends Analyzer {
} }
/** /**
* @desc Based on ExportsAnalyzerResult of reference project(s) (for instance lion-based-ui) * Based on ExportsAnalyzerResult of reference project(s) (for instance lion-based-ui)
* and targetClassesAnalyzerResult of search-targets (for instance my-app-using-lion-based-ui), * and targetClassesAnalyzerResult of search-targets (for instance my-app-using-lion-based-ui),
* an overview is returned of all matching imports and exports. * an overview is returned of all matching imports and exports.
* @param {MatchSubclassesConfig} customConfig * @param {MatchSubclassesConfig} customConfig
@ -357,5 +357,3 @@ class MatchSubclassesAnalyzer extends Analyzer {
return this._finalize(queryOutput, cfg); return this._finalize(queryOutput, cfg);
} }
} }
module.exports = MatchSubclassesAnalyzer;

View file

@ -1,5 +1,5 @@
const pathLib = require('path'); import pathLib from 'path';
const { LogService } = require('../../core/LogService.js'); import { LogService } from '../../core/LogService.js';
const /** @type {AnalyzerOptions} */ options = { const /** @type {AnalyzerOptions} */ options = {
filterSpecifier(results, targetSpecifier, specifiersKey) { filterSpecifier(results, targetSpecifier, specifiersKey) {
@ -77,7 +77,7 @@ function sortBySpecifier(analyzerResult, customConfig) {
return /** @type {AnalyzerQueryResult} */ resultsBySpecifier; return /** @type {AnalyzerQueryResult} */ resultsBySpecifier;
} }
module.exports = { export default {
name: 'sort-by-specifier', name: 'sort-by-specifier',
execute: sortBySpecifier, execute: sortBySpecifier,
compatibleAnalyzers: ['find-imports', 'find-exports'], compatibleAnalyzers: ['find-imports', 'find-exports'],

View file

@ -1,22 +1,22 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
const semver = require('semver'); import semver from 'semver';
const pathLib = require('path'); import pathLib from 'path';
const { LogService } = require('./LogService.js'); import { LogService } from './LogService.js';
const { QueryService } = require('./QueryService.js'); import { QueryService } from './QueryService.js';
const { ReportService } = require('./ReportService.js'); import { ReportService } from './ReportService.js';
const { InputDataService } = require('./InputDataService.js'); import { InputDataService } from './InputDataService.js';
const { toPosixPath } = require('../utils/to-posix-path.js'); import { toPosixPath } from '../utils/to-posix-path.js';
const { memoize } = require('../utils/memoize.js'); import { memoize } from '../utils/memoize.js';
const { getFilePathRelativeFromRoot } = require('../utils/get-file-path-relative-from-root.js'); import { getFilePathRelativeFromRoot } from '../utils/get-file-path-relative-from-root.js';
/** /**
* @typedef {import('../types/core').AnalyzerName} AnalyzerName * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../types/core').QueryOutput} QueryOutput * @typedef {import('../../../types/index.js').QueryOutput} QueryOutput
* @typedef {import('../types/core').ProjectInputData} ProjectInputData * @typedef {import('../../../types/index.js').ProjectInputData} ProjectInputData
* @typedef {import('../types/core').ProjectInputDataWithMeta} ProjectInputDataWithMeta * @typedef {import('../../../types/index.js').ProjectInputDataWithMeta} ProjectInputDataWithMeta
* @typedef {import('../types/core').AnalyzerQueryResult} AnalyzerQueryResult * @typedef {import('../../../types/index.js').AnalyzerQueryResult} AnalyzerQueryResult
* @typedef {import('../types/core').MatchAnalyzerConfig} MatchAnalyzerConfig * @typedef {import('../../../types/index.js').MatchAnalyzerConfig} MatchAnalyzerConfig
*/ */
/** /**
@ -140,14 +140,14 @@ const checkForMatchCompatibility = memoize(
const targetPkg = InputDataService.getPackageJson(targetPath); const targetPkg = InputDataService.getPackageJson(targetPath);
const allTargetDeps = [ const allTargetDeps = [
...Object.entries(targetPkg.devDependencies || {}), ...Object.entries(targetPkg?.devDependencies || {}),
...Object.entries(targetPkg.dependencies || {}), ...Object.entries(targetPkg?.dependencies || {}),
]; ];
const importEntry = allTargetDeps.find(([name]) => referencePkg.name === name); const importEntry = allTargetDeps.find(([name]) => referencePkg?.name === name);
if (!importEntry) { if (!importEntry) {
return { compatible: false, reason: 'no-dependency' }; return { compatible: false, reason: 'no-dependency' };
} }
if (!semver.satisfies(referencePkg.version, importEntry[1])) { if (referencePkg?.version && !semver.satisfies(referencePkg.version, importEntry[1])) {
return { compatible: false, reason: 'no-matched-version' }; return { compatible: false, reason: 'no-matched-version' };
} }
return { compatible: true }; return { compatible: true };
@ -164,14 +164,15 @@ function unwindJsonResult(targetOrReferenceProjectResult) {
return { queryOutput, analyzerMeta }; return { queryOutput, analyzerMeta };
} }
class Analyzer { export class Analyzer {
static requiresReference = false; static requiresReference = false;
/** @type {AnalyzerName|''} */ /** @type {AnalyzerName} */
static analyzerName = ''; static analyzerName = '';
name = /** @type {typeof Analyzer} */ (this.constructor).analyzerName; name = /** @type {typeof Analyzer} */ (this.constructor).analyzerName;
/** @type {'babel'|'swc-to-babel'|'swc-to-babel'} */
requiredAst = 'babel'; requiredAst = 'babel';
/** /**
@ -196,7 +197,7 @@ class Analyzer {
*/ */
_prepare(cfg) { _prepare(cfg) {
LogService.debug(`Analyzer "${this.name}": started _prepare method`); LogService.debug(`Analyzer "${this.name}": started _prepare method`);
this.constructor.__unwindProvidedResults(cfg); /** @type {typeof Analyzer} */ (this.constructor).__unwindProvidedResults(cfg);
if (!cfg.targetProjectResult) { if (!cfg.targetProjectResult) {
this.targetProjectMeta = InputDataService.getProjectMeta(cfg.targetProjectPath); this.targetProjectMeta = InputDataService.getProjectMeta(cfg.targetProjectPath);
@ -330,7 +331,10 @@ class Analyzer {
/** /**
* Create ASTs for our inputData * Create ASTs for our inputData
*/ */
const astDataProjects = await QueryService.addAstToProjectsData(finalTargetData, 'babel'); const astDataProjects = await QueryService.addAstToProjectsData(
finalTargetData,
this.requiredAst,
);
return analyzePerAstFile(astDataProjects[0], traverseEntryFn); return analyzePerAstFile(astDataProjects[0], traverseEntryFn);
} }
@ -384,5 +388,3 @@ class Analyzer {
return result; return result;
} }
} }
module.exports = { Analyzer };

View file

@ -1,15 +1,18 @@
const babelParser = require('@babel/parser'); import babelParser from '@babel/parser';
const parse5 = require('parse5'); import * as parse5 from 'parse5';
const traverseHtml = require('../utils/traverse-html.js'); import swc from '@swc/core';
const { LogService } = require('./LogService.js'); import { traverseHtml } from '../utils/traverse-html.js';
import { LogService } from './LogService.js';
import { guardedSwcToBabel } from '../utils/guarded-swc-to-babel.js';
/** /**
* @typedef {import("@babel/types").File} File * @typedef {import("@babel/types").File} File
* @typedef {import("@swc/core").Module} SwcAstModule
* @typedef {import("@babel/parser").ParserOptions} ParserOptions * @typedef {import("@babel/parser").ParserOptions} ParserOptions
* @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
*/ */
class AstService { export class AstService {
/** /**
* Compiles an array of file paths using Babel. * Compiles an array of file paths using Babel.
* @param {string} code * @param {string} code
@ -31,6 +34,24 @@ class AstService {
return ast; return ast;
} }
/**
* Compiles an array of file paths using Babel.
* @param {string} code
* @param {ParserOptions} parserOptions
* @returns {File}
*/
static _getSwcToBabelAst(code, parserOptions = {}) {
if (this.fallbackToBabel) {
return this._getBabelAst(code, parserOptions);
}
const ast = swc.parseSync(code, {
syntax: 'typescript',
// importAssertions: true,
...parserOptions,
});
return guardedSwcToBabel(ast, code);
}
/** /**
* Combines all script tags as if it were one js file. * Combines all script tags as if it were one js file.
* @param {string} htmlCode * @param {string} htmlCode
@ -56,7 +77,7 @@ class AstService {
/** /**
* Returns the Babel AST * Returns the Babel AST
* @param { string } code * @param { string } code
* @param { 'babel' } astType * @param { 'babel'|'swc-to-babel' } astType
* @param { {filePath?: PathFromSystemRoot} } options * @param { {filePath?: PathFromSystemRoot} } options
* @returns {File|undefined} * @returns {File|undefined}
*/ */
@ -64,11 +85,21 @@ class AstService {
static getAst(code, astType, { filePath } = {}) { static getAst(code, astType, { filePath } = {}) {
// eslint-disable-next-line default-case // eslint-disable-next-line default-case
try { try {
if (astType === 'babel') {
return this._getBabelAst(code); return this._getBabelAst(code);
}
if (astType === 'swc-to-babel') {
return this._getSwcToBabelAst(code);
}
throw new Error(`astType "${astType}" not supported.`);
} catch (e) { } catch (e) {
LogService.error(`Error when parsing "${filePath}":/n${e}`); LogService.error(`Error when parsing "${filePath}":/n${e}`);
} }
} }
} }
/**
module.exports = { AstService }; * This option can be used as a last resort when an swc AST combined with swc-to-babel, is backwards incompatible
* (for instance when @babel/generator expects a different ast structure and fails).
* Analyzers should use guarded-swc-to-babel util.
*/
AstService.fallbackToBabel = false;

View file

@ -1,39 +1,39 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
const fs = require('fs'); import fs from 'fs';
const pathLib = require('path'); import pathLib from 'path';
const child_process = require('child_process'); // eslint-disable-line camelcase import child_process from 'child_process'; // eslint-disable-line camelcase
const glob = require('glob'); import glob from 'glob';
const anymatch = require('anymatch'); import anymatch from 'anymatch';
// @ts-expect-error // @ts-expect-error
const isNegatedGlob = require('is-negated-glob'); import isNegatedGlob from 'is-negated-glob';
const { LogService } = require('./LogService.js'); import { LogService } from './LogService.js';
const { AstService } = require('./AstService.js'); import { AstService } from './AstService.js';
const { getFilePathRelativeFromRoot } = require('../utils/get-file-path-relative-from-root.js'); import { getFilePathRelativeFromRoot } from '../utils/get-file-path-relative-from-root.js';
const { toPosixPath } = require('../utils/to-posix-path.js'); import { toPosixPath } from '../utils/to-posix-path.js';
const { memoize } = require('../utils/memoize.js'); import { memoize } from '../utils/memoize.js';
/** /**
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult * @typedef {import('../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult
* @typedef {import('../types/analyzers').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry * @typedef {import('../../../types/index.js').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
* @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot * @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
* @typedef {import('../types/core').PathRelative} PathRelative * @typedef {import('../../../types/index.js').PathRelative} PathRelative
* @typedef {import('../types/core').QueryConfig} QueryConfig * @typedef {import('../../../types/index.js').QueryConfig} QueryConfig
* @typedef {import('../types/core').QueryResult} QueryResult * @typedef {import('../../../types/index.js').QueryResult} QueryResult
* @typedef {import('../types/core').FeatureQueryConfig} FeatureQueryConfig * @typedef {import('../../../types/index.js').FeatureQueryConfig} FeatureQueryConfig
* @typedef {import('../types/core').SearchQueryConfig} SearchQueryConfig * @typedef {import('../../../types/index.js').SearchQueryConfig} SearchQueryConfig
* @typedef {import('../types/core').AnalyzerQueryConfig} AnalyzerQueryConfig * @typedef {import('../../../types/index.js').AnalyzerQueryConfig} AnalyzerQueryConfig
* @typedef {import('../types/core').Feature} Feature * @typedef {import('../../../types/index.js').Feature} Feature
* @typedef {import('../types/core').AnalyzerConfig} AnalyzerConfig * @typedef {import('../../../types/index.js').AnalyzerConfig} AnalyzerConfig
* @typedef {import('../types/core').Analyzer} Analyzer * @typedef {import('../../../types/index.js').Analyzer} Analyzer
* @typedef {import('../types/core').AnalyzerName} AnalyzerName * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../types/core').GatherFilesConfig} GatherFilesConfig * @typedef {import('../../../types/index.js').GatherFilesConfig} GatherFilesConfig
* @typedef {import('../types/core').AnalyzerQueryResult} AnalyzerQueryResult * @typedef {import('../../../types/index.js').AnalyzerQueryResult} AnalyzerQueryResult
* @typedef {import('../types/core').ProjectInputData} ProjectInputData * @typedef {import('../../../types/index.js').ProjectInputData} ProjectInputData
* @typedef {import('../types/core').ProjectInputDataWithMeta} ProjectInputDataWithMeta * @typedef {import('../../../types/index.js').ProjectInputDataWithMeta} ProjectInputDataWithMeta
* @typedef {import('../types/core').Project} Project * @typedef {import('../../../types/index.js').Project} Project
* @typedef {import('../types/core').ProjectName} ProjectName * @typedef {import('../../../types/index.js').ProjectName} ProjectName
* @typedef {import('../types/core').PackageJson} PackageJson * @typedef {import('../../../types/index.js').PackageJson} PackageJson
* @typedef {{path:PathFromSystemRoot; name:ProjectName}} ProjectNameAndPath * @typedef {{path:PathFromSystemRoot; name:ProjectName}} ProjectNameAndPath
*/ */
@ -219,6 +219,10 @@ function stripDotSlashFromLocalPath(localPathWithDotSlash) {
return localPathWithDotSlash.replace(/^\.\//, ''); return localPathWithDotSlash.replace(/^\.\//, '');
} }
/**
* @param {string} localPathWithoutDotSlash
* @returns {string}
*/
function normalizeLocalPathWithDotSlash(localPathWithoutDotSlash) { function normalizeLocalPathWithDotSlash(localPathWithoutDotSlash) {
if (!localPathWithoutDotSlash.startsWith('.')) { if (!localPathWithoutDotSlash.startsWith('.')) {
return `./${localPathWithoutDotSlash}`; return `./${localPathWithoutDotSlash}`;
@ -227,7 +231,7 @@ function normalizeLocalPathWithDotSlash(localPathWithoutDotSlash) {
} }
/** /**
* @param {{val:object|string;nodeResolveMode:string}} opts * @param {{valObjOrStr:object|string;nodeResolveMode:string}} opts
* @returns {string|null} * @returns {string|null}
*/ */
function getStringOrObjectValOfExportMapEntry({ valObjOrStr, nodeResolveMode }) { function getStringOrObjectValOfExportMapEntry({ valObjOrStr, nodeResolveMode }) {
@ -248,7 +252,7 @@ function getStringOrObjectValOfExportMapEntry({ valObjOrStr, nodeResolveMode })
* *
* Also serves as SSOT in many other contexts wrt data locations and gathering * Also serves as SSOT in many other contexts wrt data locations and gathering
*/ */
class InputDataService { export class InputDataService {
/** /**
* Create an array of ProjectData * Create an array of ProjectData
* @param {(PathFromSystemRoot|ProjectInputData)[]} projectPaths * @param {(PathFromSystemRoot|ProjectInputData)[]} projectPaths
@ -708,5 +712,3 @@ InputDataService.getMonoRepoPackages = memoize(InputDataService.getMonoRepoPacka
InputDataService.createDataObject = memoize(InputDataService.createDataObject); InputDataService.createDataObject = memoize(InputDataService.createDataObject);
InputDataService.getPackageJson = getPackageJson; InputDataService.getPackageJson = getPackageJson;
module.exports = { InputDataService };

View file

@ -1,9 +1,19 @@
const pathLib = require('path'); import pathLib from 'path';
const chalk = require('chalk'); import fs from 'fs';
const fs = require('fs');
const { log } = console; const { log } = console;
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
fgRed: '\x1b[31m',
fgGreen: '\x1b[32m',
fgYellow: '\x1b[33m',
fgGray: '\x1b[90m',
fgBlue: '\x1b[34m',
};
/** /**
* @param {string} [title] * @param {string} [title]
* @returns {string} * @returns {string}
@ -12,7 +22,7 @@ function printTitle(title) {
return `${title ? `${title}\n` : ''}`; return `${title ? `${title}\n` : ''}`;
} }
class LogService { export class LogService {
/** /**
* @param {string} text * @param {string} text
* @param {string} [title] * @param {string} [title]
@ -22,7 +32,7 @@ class LogService {
return; return;
} }
log(chalk.bgCyanBright.black.bold(` debug${printTitle(title)}`), text); log(colors.bright, ` debug${printTitle(title)}`, colors.reset, text);
// @ts-ignore // @ts-ignore
this._logHistory.push(`- debug -${printTitle(title)} ${text}`); this._logHistory.push(`- debug -${printTitle(title)} ${text}`);
} }
@ -36,7 +46,7 @@ class LogService {
return; return;
} }
log(chalk.bgYellowBright.black.bold(`warning${printTitle(title)}`), text); log(colors.fgYellow, `warning${printTitle(title)}`, colors.reset, text);
// @ts-ignore // @ts-ignore
this._logHistory.push(`- warning -${printTitle(title)} ${text}`); this._logHistory.push(`- warning -${printTitle(title)} ${text}`);
} }
@ -57,7 +67,7 @@ class LogService {
return; return;
} }
log(chalk.bgRedBright.black.bold(` error${printTitle(title)}`), text); log(colors.fgRed, ` error${printTitle(title)}`, colors.reset, text);
} }
/** /**
@ -71,7 +81,7 @@ class LogService {
return; return;
} }
log(chalk.bgGreen.black.bold(`success${printTitle(title)}`), text); log(colors.fgGreen, `success${printTitle(title)}`, colors.reset, text);
} }
/** /**
@ -84,7 +94,7 @@ class LogService {
if (this.allMuted) { if (this.allMuted) {
return; return;
} }
log(chalk.bgBlue.black.bold(` info${printTitle(title)}`), text); log(colors.fgBlue, ` info${printTitle(title)}`, colors.reset, text);
} }
/** /**
@ -120,5 +130,3 @@ LogService.throwsOnError = false;
/** @type {string[]} */ /** @type {string[]} */
LogService._logHistory = []; LogService._logHistory = [];
module.exports = { LogService };

View file

@ -1,32 +1,33 @@
const deepmerge = require('deepmerge'); import child_process from 'child_process'; // eslint-disable-line camelcase
const child_process = require('child_process'); // eslint-disable-line camelcase import path from 'path';
const { AstService } = require('./AstService.js'); import { AstService } from './AstService.js';
const { LogService } = require('./LogService.js'); import { LogService } from './LogService.js';
const { getFilePathRelativeFromRoot } = require('../utils/get-file-path-relative-from-root.js'); import { getFilePathRelativeFromRoot } from '../utils/get-file-path-relative-from-root.js';
const { memoize } = require('../utils/memoize.js'); import { memoize } from '../utils/memoize.js';
import { getCurrentDir } from '../utils/get-current-dir.js';
/** /**
* @typedef {import('../types/analyzers').FindImportsAnalyzerResult} FindImportsAnalyzerResult * @typedef {import('./Analyzer.js').Analyzer} Analyzer
* @typedef {import('../types/analyzers').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry * @typedef {import('../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult
* @typedef {import('../types/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot * @typedef {import('../../../types/index.js').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
* @typedef {import('../types/core').QueryConfig} QueryConfig * @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
* @typedef {import('../types/core').QueryResult} QueryResult * @typedef {import('../../../types/index.js').QueryConfig} QueryConfig
* @typedef {import('../types/core').FeatureQueryConfig} FeatureQueryConfig * @typedef {import('../../../types/index.js').QueryResult} QueryResult
* @typedef {import('../types/core').SearchQueryConfig} SearchQueryConfig * @typedef {import('../../../types/index.js').FeatureQueryConfig} FeatureQueryConfig
* @typedef {import('../types/core').AnalyzerQueryConfig} AnalyzerQueryConfig * @typedef {import('../../../types/index.js').SearchQueryConfig} SearchQueryConfig
* @typedef {import('../types/core').Feature} Feature * @typedef {import('../../../types/index.js').AnalyzerQueryConfig} AnalyzerQueryConfig
* @typedef {import('../types/core').AnalyzerConfig} AnalyzerConfig * @typedef {import('../../../types/index.js').Feature} Feature
* @typedef {import('../types/core').Analyzer} Analyzer * @typedef {import('../../../types/index.js').ProjectInputData} ProjectInputData
* @typedef {import('../types/core').AnalyzerName} AnalyzerName * @typedef {import('../../../types/index.js').AnalyzerConfig} AnalyzerConfig
* @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('../types/core').GatherFilesConfig} GatherFilesConfig * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../types/core').AnalyzerQueryResult} AnalyzerQueryResult * @typedef {import('../../../types/index.js').GatherFilesConfig} GatherFilesConfig
* @typedef {import('../types/core').ProjectInputData} ProjectInputData * @typedef {import('../../../types/index.js').AnalyzerQueryResult} AnalyzerQueryResult
*/ */
const astProjectsDataCache = new Map(); const astProjectsDataCache = new Map();
class QueryService { export class QueryService {
/** /**
* @param {string} regexString string for 'free' regex searches. * @param {string} regexString string for 'free' regex searches.
* @returns {SearchQueryConfig} * @returns {SearchQueryConfig}
@ -104,18 +105,29 @@ class QueryService {
/** /**
* Retrieves the default export found in ./program/analyzers/find-import.js * Retrieves the default export found in ./program/analyzers/find-import.js
* @param {string|typeof Analyzer} analyzerObjectOrString * @param {typeof Analyzer} analyzerCtor
* @param {AnalyzerConfig} [analyzerConfig] * @param {AnalyzerConfig} [analyzerConfig]
* @returns {AnalyzerQueryConfig} * @returns {Promise<AnalyzerQueryConfig>}
*/ */
static getQueryConfigFromAnalyzer(analyzerObjectOrString, analyzerConfig) { static async getQueryConfigFromAnalyzer(analyzerObjectOrString, analyzerConfig) {
let analyzer; let analyzer;
if (typeof analyzerObjectOrString === 'string') { if (typeof analyzerObjectOrString === 'string') {
// Get it from our location(s) of predefined analyzers. // Get it from our location(s) of predefined analyzers.
// Mainly needed when this method is called via cli // Mainly needed when this method is called via cli
try { try {
// eslint-disable-next-line import/no-dynamic-require, global-require // eslint-disable-next-line import/no-dynamic-require, global-require
analyzer = /** @type {Analyzer} */ (require(`../analyzers/${analyzerObjectOrString}`)); const module = /** @type {Analyzer} */ (
await import(
path.join(
'file:///',
path.resolve(
getCurrentDir(import.meta.url),
`../analyzers/${analyzerObjectOrString}.js`,
),
)
)
);
analyzer = module.default;
} catch (e) { } catch (e) {
LogService.error(e.toString()); LogService.error(e.toString());
process.exit(1); process.exit(1);
@ -134,19 +146,17 @@ class QueryService {
/** /**
* Search via unix grep * Search via unix grep
* @param {InputData} inputData * @param {ProjectInputData} inputData
* @param {FeatureQueryConfig|SearchQueryConfig} queryConfig * @param {FeatureQueryConfig|SearchQueryConfig} queryConfig
* @param {{hasVerboseReporting:boolean;gatherFilesConfig:GatherFilesConfig}} [customConfig] * @param {{hasVerboseReporting:boolean;gatherFilesConfig:GatherFilesConfig}} [customConfig]
* @returns {Promise<QueryResult>} * @returns {Promise<QueryResult>}
*/ */
static async grepSearch(inputData, queryConfig, customConfig) { static async grepSearch(inputData, queryConfig, customConfig) {
const cfg = deepmerge( const cfg = {
{
hasVerboseReporting: false, hasVerboseReporting: false,
gatherFilesConfig: {}, gatherFilesConfig: {},
}, ...customConfig,
customConfig, };
);
const results = []; const results = [];
// 1. Analyze the type of query from the QueryConfig (for instance 'feature' or 'search'). // 1. Analyze the type of query from the QueryConfig (for instance 'feature' or 'search').
@ -229,7 +239,7 @@ class QueryService {
/** /**
* @param {ProjectInputData[]} projectsData * @param {ProjectInputData[]} projectsData
* @param {'babel'|'typescript'|'es-module-lexer'} requiredAst * @param {'babel'|'swc-to-babel'} requiredAst
*/ */
static async addAstToProjectsData(projectsData, requiredAst) { static async addAstToProjectsData(projectsData, requiredAst) {
return projectsData.map(projectData => { return projectsData.map(projectData => {
@ -237,12 +247,13 @@ class QueryService {
if (cachedData) { if (cachedData) {
return cachedData; return cachedData;
} }
const resultEntries = projectData.entries.map(entry => { const resultEntries = projectData.entries.map(entry => {
const ast = AstService.getAst(entry.context.code, requiredAst, { filePath: entry.file }); const ast = AstService.getAst(entry.context.code, requiredAst, { filePath: entry.file });
return { ...entry, ast }; return { ...entry, ast };
}); });
const astData = { ...projectData, entries: resultEntries }; const astData = { ...projectData, entries: resultEntries };
this._addToProjectsDataCache(projectData.project.path, astData); this._addToProjectsDataCache(`${projectData.project.path}#${requiredAst}`, astData);
return astData; return astData;
}); });
} }
@ -251,12 +262,12 @@ class QueryService {
* We need to make sure we don't run into memory issues (ASTs are huge), * We need to make sure we don't run into memory issues (ASTs are huge),
* so we only store one project in cache now. This will be a performance benefit for * so we only store one project in cache now. This will be a performance benefit for
* lion-based-ui-cli, that runs providence consecutively for the same project * lion-based-ui-cli, that runs providence consecutively for the same project
* TODO: instead of storing one result in cache, use sizeof and a memory ;imit * TODO: instead of storing one result in cache, use sizeof and a memory limit
* to allow for more projects * to allow for more projects
* @param {string} path * @param {string} pathAndRequiredAst
* @param {InputData} astData * @param {ProjectInputData} astData
*/ */
static _addToProjectsDataCache(path, astData) { static _addToProjectsDataCache(pathAndRequiredAst, astData) {
if (this.cacheDisabled) { if (this.cacheDisabled) {
return; return;
} }
@ -266,7 +277,7 @@ class QueryService {
if (astProjectsDataCache.size >= 2) { if (astProjectsDataCache.size >= 2) {
astProjectsDataCache.delete(astProjectsDataCache.keys()[0]); astProjectsDataCache.delete(astProjectsDataCache.keys()[0]);
} }
astProjectsDataCache.set(path, astData); astProjectsDataCache.set(pathAndRequiredAst, astData);
} }
/** /**
@ -318,14 +329,12 @@ class QueryService {
* @returns * @returns
*/ */
static _performGrep(searchPath, regex, customConfig) { static _performGrep(searchPath, regex, customConfig) {
const cfg = deepmerge( const cfg = {
{
count: false, count: false,
gatherFilesConfig: {}, gatherFilesConfig: {},
hasDebugEnabled: false, hasDebugEnabled: false,
}, ...customConfig,
customConfig, };
);
const /** @type {string[]} */ ext = cfg.gatherFilesConfig.extensions; const /** @type {string[]} */ ext = cfg.gatherFilesConfig.extensions;
const include = ext ? `--include="\\.(${ext.map(e => e.slice(1)).join('|')})" ` : ''; const include = ext ? `--include="\\.(${ext.map(e => e.slice(1)).join('|')})" ` : '';
@ -347,7 +356,4 @@ class QueryService {
} }
} }
QueryService.cacheDisabled = false; QueryService.cacheDisabled = false;
QueryService.addAstToProjectsData = memoize(QueryService.addAstToProjectsData); QueryService.addAstToProjectsData = memoize(QueryService.addAstToProjectsData);
module.exports = { QueryService };

View file

@ -1,17 +1,16 @@
const fs = require('fs'); import fs from 'fs';
const pathLib = require('path'); import pathLib from 'path';
const getHash = require('../utils/get-hash.js'); import { getHash } from '../utils/get-hash.js';
const { memoize } = require('../utils/memoize.js'); import { memoize } from '../utils/memoize.js';
// const memoize = fn => fn;
/** /**
* @typedef {import('../types/core').Project} Project * @typedef {import('../../../types/index.js').Project} Project
* @typedef {import('../types/core').ProjectName} ProjectName * @typedef {import('../../../types/index.js').ProjectName} ProjectName
* @typedef {import('../types/core').AnalyzerQueryResult} AnalyzerQueryResult * @typedef {import('../../../types/index.js').AnalyzerQueryResult} AnalyzerQueryResult
* @typedef {import('../types/core').AnalyzerConfig} AnalyzerConfig * @typedef {import('../../../types/index.js').AnalyzerConfig} AnalyzerConfig
* @typedef {import('../types/core').AnalyzerName} AnalyzerName * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('../types/core').QueryResult} QueryResult * @typedef {import('../../../types/index.js').QueryResult} QueryResult
* @typedef {import('../types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
*/ */
/** /**
@ -30,7 +29,7 @@ function createResultIdentifier(searchP, cfg, refP) {
return `${format(searchP)}${refP ? `_+_${format(refP)}` : ''}__${cfgHash}`; return `${format(searchP)}${refP ? `_+_${format(refP)}` : ''}__${cfgHash}`;
} }
class ReportService { export class ReportService {
/** /**
* Prints queryResult report to console * Prints queryResult report to console
* @param {QueryResult} queryResult * @param {QueryResult} queryResult
@ -130,5 +129,3 @@ class ReportService {
} }
ReportService.createIdentifier = memoize(ReportService.createIdentifier); ReportService.createIdentifier = memoize(ReportService.createIdentifier);
ReportService.getCachedResult = memoize(ReportService.getCachedResult); ReportService.getCachedResult = memoize(ReportService.getCachedResult);
module.exports = { ReportService };

View file

@ -1,18 +1,18 @@
const { performance } = require('perf_hooks'); import { performance } from 'perf_hooks';
const deepmerge = require('deepmerge'); import { ReportService } from './core/ReportService.js';
const { ReportService } = require('./core/ReportService.js'); import { InputDataService } from './core/InputDataService.js';
const { InputDataService } = require('./core/InputDataService.js'); import { LogService } from './core/LogService.js';
const { LogService } = require('./core/LogService.js'); import { QueryService } from './core/QueryService.js';
const { QueryService } = require('./core/QueryService.js'); import { AstService } from './core/AstService.js';
/** /**
* @typedef {import('./types/core').ProvidenceConfig} ProvidenceConfig * @typedef {import('../../types/index.js').ProvidenceConfig} ProvidenceConfig
* @typedef {import('./types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('./types/core').QueryResult} QueryResult * @typedef {import('../../types/index.js').QueryResult} QueryResult
* @typedef {import('./types/core').AnalyzerQueryResult} AnalyzerQueryResult * @typedef {import('../../types/index.js').AnalyzerQueryResult} AnalyzerQueryResult
* @typedef {import('./types/core').QueryConfig} QueryConfig * @typedef {import('../../types/index.js').QueryConfig} QueryConfig
* @typedef {import('./types/core').AnalyzerQueryConfig} AnalyzerQueryConfig * @typedef {import('../../types/index.js').AnalyzerQueryConfig} AnalyzerQueryConfig
* @typedef {import('./types/core').GatherFilesConfig} GatherFilesConfig * @typedef {import('../../types/index.js').GatherFilesConfig} GatherFilesConfig
*/ */
/** /**
@ -172,13 +172,12 @@ async function handleRegexSearch(queryConfig, cfg, inputData) {
* *
* @param {QueryConfig} queryConfig a query configuration object containing analyzerOptions. * @param {QueryConfig} queryConfig a query configuration object containing analyzerOptions.
* @param {Partial<ProvidenceConfig>} customConfig * @param {Partial<ProvidenceConfig>} customConfig
* @return {Promise<QueryResult[]>}
*/ */
async function providenceMain(queryConfig, customConfig) { export async function providence(queryConfig, customConfig) {
const tStart = performance.now(); const tStart = performance.now();
const cfg = /** @type {ProvidenceConfig} */ ( const cfg = /** @type {ProvidenceConfig} */ ({
deepmerge(
{
queryMethod: 'grep', queryMethod: 'grep',
// This is a merge of all 'main entry projects' // This is a merge of all 'main entry projects'
// found in search-targets, including their children // found in search-targets, including their children
@ -196,10 +195,9 @@ async function providenceMain(queryConfig, customConfig) {
measurePerformance: false, measurePerformance: false,
/** Allows to navigate to source file in code editor */ /** Allows to navigate to source file in code editor */
addSystemPathsInResult: false, addSystemPathsInResult: false,
}, fallbackToBabel: false,
customConfig, ...customConfig,
) });
);
if (cfg.debugEnabled) { if (cfg.debugEnabled) {
LogService.debugEnabled = true; LogService.debugEnabled = true;
@ -209,6 +207,10 @@ async function providenceMain(queryConfig, customConfig) {
InputDataService.referenceProjectPaths = cfg.referenceProjectPaths; InputDataService.referenceProjectPaths = cfg.referenceProjectPaths;
} }
if (cfg.fallbackToBabel) {
AstService.fallbackToBabel = true;
}
let queryResults; let queryResults;
if (queryConfig.type === 'ast-analyzer') { if (queryConfig.type === 'ast-analyzer') {
queryResults = await handleAnalyzer(queryConfig, cfg); queryResults = await handleAnalyzer(queryConfig, cfg);
@ -240,6 +242,6 @@ async function providenceMain(queryConfig, customConfig) {
return queryResults; return queryResults;
} }
module.exports = { export const _providenceModule = {
providence: providenceMain, providence,
}; };

View file

@ -1,6 +0,0 @@
export * from './find-classes';
export * from './find-customelements';
export * from './find-exports';
export * from './find-imports';
export * from './match-imports';
export * from './match-subclasses';

View file

@ -1,3 +0,0 @@
export * from './core';
export * from './Analyzer';
export * from './QueryService';

View file

@ -1,3 +0,0 @@
export * from './core';
export * from './analyzers';
export * from './utils';

View file

@ -1 +0,0 @@
export type MemoizeFunction<T> = (fn: T, storage?: object) => T;

View file

@ -1,6 +1,6 @@
/** /**
* @typedef {import('../types/core/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot * @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
* @typedef {import('../types/core/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
*/ */
/** /**
@ -11,8 +11,6 @@
* @param {PathFromSystemRoot} projectRoot * @param {PathFromSystemRoot} projectRoot
* @returns {PathRelativeFromProjectRoot} * @returns {PathRelativeFromProjectRoot}
*/ */
function getFilePathRelativeFromRoot(absolutePath, projectRoot) { export function getFilePathRelativeFromRoot(absolutePath, projectRoot) {
return /** @type {PathRelativeFromProjectRoot} */ (absolutePath.replace(projectRoot, '.')); return /** @type {PathRelativeFromProjectRoot} */ (absolutePath.replace(projectRoot, '.'));
} }
module.exports = { getFilePathRelativeFromRoot };

View file

@ -2,7 +2,7 @@
* @param {string|object} inputValue * @param {string|object} inputValue
* @returns {number} * @returns {number}
*/ */
function getHash(inputValue) { export function getHash(inputValue) {
if (typeof inputValue === 'object') { if (typeof inputValue === 'object') {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
inputValue = JSON.stringify(inputValue); inputValue = JSON.stringify(inputValue);
@ -14,5 +14,3 @@ function getHash(inputValue) {
0, 0,
); );
} }
module.exports = getHash;

View file

@ -1,11 +1,22 @@
const fs = require('fs'); import fs from 'fs';
const path = require('path'); import path from 'path';
const babelTraversePkg = require('@babel/traverse'); import babelTraversePkg from '@babel/traverse';
const { AstService } = require('../core/AstService.js'); import { AstService } from '../core/AstService.js';
const { trackDownIdentifier } = require('../analyzers/helpers/track-down-identifier.js'); import { trackDownIdentifier } from '../analyzers/helpers/track-down-identifier.js';
const { toPosixPath } = require('./to-posix-path.js'); import { toPosixPath } from './to-posix-path.js';
function getFilePathOrExternalSource({ rootPath, localPath }) { /**
* @typedef {import('@babel/types').Node} Node
* @typedef {import('@babel/traverse').NodePath} NodePath
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
*/
/**
* @param {{rootPath:PathFromSystemRoot; localPath:PathRelativeFromProjectRoot}} opts
* @returns
*/
export function getFilePathOrExternalSource({ rootPath, localPath }) {
if (!localPath.startsWith('.')) { if (!localPath.startsWith('.')) {
// We are not resolving external files like '@lion/input-amount/x.js', // We are not resolving external files like '@lion/input-amount/x.js',
// but we give a 100% score if from and to are same here.. // but we give a 100% score if from and to are same here..
@ -27,9 +38,9 @@ function getFilePathOrExternalSource({ rootPath, localPath }) {
* - Is it a ref? Call ourselves with referencedIdentifierName ('x' in example above) * - Is it a ref? Call ourselves with referencedIdentifierName ('x' in example above)
* - is it a non ref declaration? Return the path of the node * - is it a non ref declaration? Return the path of the node
* @param {{ referencedIdentifierName:string, globalScopeBindings:BabelBinding; }} opts * @param {{ referencedIdentifierName:string, globalScopeBindings:BabelBinding; }} opts
* @returns {BabelNodePath} * @returns {NodePath}
*/ */
function getReferencedDeclaration({ referencedIdentifierName, globalScopeBindings }) { export function getReferencedDeclaration({ referencedIdentifierName, globalScopeBindings }) {
const [, refDeclaratorBinding] = Object.entries(globalScopeBindings).find( const [, refDeclaratorBinding] = Object.entries(globalScopeBindings).find(
([key]) => key === referencedIdentifierName, ([key]) => key === referencedIdentifierName,
); );
@ -52,22 +63,24 @@ function getReferencedDeclaration({ referencedIdentifierName, globalScopeBinding
} }
/** /**
* * @param {{ filePath: PathFromSystemRoot; exportedIdentifier: string; projectRootPath: PathFromSystemRoot }} opts
* @param {{ filePath: string; exportedIdentifier: string; }} opts * @returns {Promise<{ sourceNodePath: string; sourceFragment: string|null; externalImportSource: string; }>}
*/ */
async function getSourceCodeFragmentOfDeclaration({ export async function getSourceCodeFragmentOfDeclaration({
filePath, filePath,
exportedIdentifier, exportedIdentifier,
projectRootPath, projectRootPath,
}) { }) {
const code = fs.readFileSync(filePath, 'utf-8'); const code = fs.readFileSync(filePath, 'utf8');
const ast = AstService.getAst(code, 'babel'); // TODO: fix swc-to-babel lib to make this compatible with 'swc-to-babel' mode of getAst
const babelAst = AstService.getAst(code, 'babel', { filePath });
/** @type {NodePath} */
let finalNodePath; let finalNodePath;
babelTraversePkg.default(ast, { babelTraversePkg.default(babelAst, {
Program(babelPath) { Program(astPath) {
babelPath.stop(); astPath.stop();
// Situations // Situations
// - Identifier is part of default export (in this case 'exportedIdentifier' is '[default]' ) // - Identifier is part of default export (in this case 'exportedIdentifier' is '[default]' )
@ -77,13 +90,14 @@ async function getSourceCodeFragmentOfDeclaration({
// - declared right away // - declared right away
// - referenced (possibly recursively) by other declaration // - referenced (possibly recursively) by other declaration
const globalScopeBindings = babelPath.get('body')[0].scope.bindings; const globalScopeBindings = astPath.get('body')[0].scope.bindings;
if (exportedIdentifier === '[default]') { if (exportedIdentifier === '[default]') {
const defaultExportPath = babelPath const defaultExportPath = astPath
.get('body') .get('body')
.find(child => child.node.type === 'ExportDefaultDeclaration'); .find(child => child.node.type === 'ExportDefaultDeclaration');
const isReferenced = defaultExportPath.node.declaration?.type === 'Identifier'; // @ts-expect-error
const isReferenced = defaultExportPath?.node.declaration?.type === 'Identifier';
if (!isReferenced) { if (!isReferenced) {
finalNodePath = defaultExportPath.get('declaration'); finalNodePath = defaultExportPath.get('declaration');
@ -94,7 +108,7 @@ async function getSourceCodeFragmentOfDeclaration({
}); });
} }
} else { } else {
const variableDeclaratorPath = babelPath.scope.getBinding(exportedIdentifier).path; const variableDeclaratorPath = astPath.scope.getBinding(exportedIdentifier).path;
const varDeclNode = variableDeclaratorPath.node; const varDeclNode = variableDeclaratorPath.node;
const isReferenced = varDeclNode.init?.type === 'Identifier'; const isReferenced = varDeclNode.init?.type === 'Identifier';
const contentPath = varDeclNode.init const contentPath = varDeclNode.init
@ -154,13 +168,10 @@ async function getSourceCodeFragmentOfDeclaration({
return { return {
sourceNodePath: finalNodePath, sourceNodePath: finalNodePath,
sourceFragment: code.slice(finalNodePath.node?.start, finalNodePath.node?.end), sourceFragment: code.slice(
finalNodePath.node?.loc?.start.index,
finalNodePath.node?.loc?.end.index,
),
externalImportSource: null, externalImportSource: null,
}; };
} }
module.exports = {
getSourceCodeFragmentOfDeclaration,
getFilePathOrExternalSource,
getReferencedDeclaration,
};

View file

@ -0,0 +1,23 @@
import toBabel from 'swc-to-babel';
/**
* @typedef {import('@babel/types').File} File
*/
/**
* Internal wrapper around swc-to-babel...
* Allows to easily switch all swc based analyzers to Babel in case
* they turn out to be not stable yet (for instance printing a transformed ast with @babel/generator)
* Checks first whether it gets a Babel ast provided or not...
* @param {*} swcOrBabelAst
* @param {string} source
* @returns {File}
*/
export function guardedSwcToBabel(swcOrBabelAst, source) {
const isSwcAst = swcOrBabelAst.type === 'Module';
if (isSwcAst) {
// @ts-ignore
return toBabel(swcOrBabelAst, source);
}
return swcOrBabelAst;
}

View file

@ -1,16 +1,8 @@
const { export {
getSourceCodeFragmentOfDeclaration, getSourceCodeFragmentOfDeclaration,
getFilePathOrExternalSource, getFilePathOrExternalSource,
} = require('./get-source-code-fragment-of-declaration.js'); } from './get-source-code-fragment-of-declaration.js';
const { memoize } = require('./memoize.js'); export { memoize } from './memoize.js';
const { toRelativeSourcePath, isRelativeSourcePath } = require('./relative-source-path.js'); export { toRelativeSourcePath, isRelativeSourcePath } from './relative-source-path.js';
// TODO: move trackdownIdentifier to utils as well // TODO: move trackdownIdentifier to utils as well
module.exports = {
memoize,
getSourceCodeFragmentOfDeclaration,
getFilePathOrExternalSource,
toRelativeSourcePath,
isRelativeSourcePath,
};

View file

@ -36,7 +36,7 @@
* console.log(tags); * console.log(tags);
* } * }
*/ */
class JsdocCommentParser { export default class JsdocCommentParser {
/** /**
* parse comment to tags. * parse comment to tags.
* @param {ASTNode} commentNode - comment node. * @param {ASTNode} commentNode - comment node.
@ -122,5 +122,3 @@ class JsdocCommentParser {
}, '*\n'); }, '*\n');
} }
} }
module.exports = JsdocCommentParser;

View file

@ -1,4 +1,4 @@
const memoizeConfig = { export const memoizeConfig = {
isCacheDisabled: false, isCacheDisabled: false,
}; };
@ -24,10 +24,10 @@ function createCachableArg(arg) {
} }
/** /**
* @param {function} functionToMemoize * @type {<T>(functionToMemoize:T, opts?:{ storage?:object; serializeObjects?: boolean }) => T}
* @param {{ storage?:object; serializeObjects?: boolean }} opts
*/ */
function memoize(functionToMemoize, { storage = {}, serializeObjects = false } = {}) { export function memoize(functionToMemoize, { storage = {}, serializeObjects = false } = {}) {
// @ts-ignore
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
return function () { return function () {
// eslint-disable-next-line prefer-rest-params // eslint-disable-next-line prefer-rest-params
@ -47,8 +47,3 @@ function memoize(functionToMemoize, { storage = {}, serializeObjects = false } =
return outcome; return outcome;
}; };
} }
module.exports = {
memoize,
memoizeConfig,
};

View file

@ -3,7 +3,11 @@ import fs from 'fs';
import { pathToFileURL } from 'url'; import { pathToFileURL } from 'url';
/** /**
* @returns {Promise<object|null>} * @typedef {import('../../../types/index.js').ProvidenceCliConf} ProvidenceCliConf
*/
/**
* @returns {Promise<{providenceConf:Partial<ProvidenceCliConf>;providenceConfRaw:string}|null>}
*/ */
async function getConf() { async function getConf() {
const confPathWithoutExtension = `${pathLib.join(process.cwd(), 'providence.conf')}`; const confPathWithoutExtension = `${pathLib.join(process.cwd(), 'providence.conf')}`;

View file

@ -24,13 +24,16 @@
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
const fs = require('fs'); import fs from 'fs';
/* istanbul ignore next */ /* istanbul ignore next */
const promisify = require('util').promisify || require('util-promisify'); import { promisify } from 'util';
const { resolve, basename, dirname, join } = require('path'); import { basename, dirname, join } from 'path';
const rpj = promisify(require('read-package-json')); import rpjSync from 'read-package-json';
const readdir = promisify(require('readdir-scoped-modules')); import readdirSync from 'readdir-scoped-modules';
const realpath = require('read-package-tree/realpath.js'); import realpath from 'read-package-tree/realpath.js';
const rpj = promisify(rpjSync);
const readdir = promisify(readdirSync);
let ID = 0; let ID = 0;
class Node { class Node {
@ -220,4 +223,5 @@ const rpt = (root, filterWith, cb, mode = 'npm') => {
rpt.Node = Node; rpt.Node = Node;
rpt.Link = Link; rpt.Link = Link;
module.exports = rpt;
export default rpt;

View file

@ -1,4 +1,4 @@
const { toPosixPath } = require('./to-posix-path.js'); import { toPosixPath } from './to-posix-path.js';
/** /**
* Determines for a source path of an import- or export specifier, whether * Determines for a source path of an import- or export specifier, whether
@ -8,7 +8,7 @@ const { toPosixPath } = require('./to-posix-path.js');
* @param {string} source source path of an import- or export specifier * @param {string} source source path of an import- or export specifier
* @returns {boolean} * @returns {boolean}
*/ */
function isRelativeSourcePath(source) { export function isRelativeSourcePath(source) {
return source.startsWith('.'); return source.startsWith('.');
} }
@ -19,8 +19,6 @@ function isRelativeSourcePath(source) {
* @param {string} fullPath like '/path/to/repo/my/file.js' * @param {string} fullPath like '/path/to/repo/my/file.js'
* @param {string} rootPath like '/path/to/repo' * @param {string} rootPath like '/path/to/repo'
*/ */
function toRelativeSourcePath(fullPath, rootPath) { export function toRelativeSourcePath(fullPath, rootPath) {
return toPosixPath(fullPath).replace(toPosixPath(rootPath), '.'); return toPosixPath(fullPath).replace(toPosixPath(rootPath), '.');
} }
module.exports = { isRelativeSourcePath, toRelativeSourcePath };

View file

@ -1,20 +1,20 @@
import pathLib from 'path';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import { LogService } from '../core/LogService.js';
import { memoize } from './memoize.js';
import { toPosixPath } from './to-posix-path.js';
/** /**
* Solution inspired by es-dev-server: * Solution inspired by es-dev-server:
* https://github.com/open-wc/open-wc/blob/master/packages/es-dev-server/src/utils/resolve-module-imports.js * https://github.com/open-wc/open-wc/blob/master/packages/es-dev-server/src/utils/resolve-module-imports.js
*/ */
/** /**
* @typedef {import('../types/core/core').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot * @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
* @typedef {import('../types/core/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../types/core/core').SpecifierSource} SpecifierSource * @typedef {import('../../../types/index.js').SpecifierSource} SpecifierSource
*/ */
const pathLib = require('path');
const { nodeResolve } = require('@rollup/plugin-node-resolve');
const { LogService } = require('../core/LogService.js');
const { memoize } = require('./memoize.js');
const { toPosixPath } = require('./to-posix-path.js');
const fakePluginContext = { const fakePluginContext = {
meta: { meta: {
// rollupVersion needed in plugin context => nodeResolvePackageJson.peerDependencies.rollup // rollupVersion needed in plugin context => nodeResolvePackageJson.peerDependencies.rollup
@ -29,7 +29,16 @@ const fakePluginContext = {
}, },
}; };
async function resolveImportPath(importee, importer, opts) { /**
* Based on importee (in a statement "import {x} from '@lion/core'", "@lion/core" is an
* importee), which can be a bare module specifier, a filename without extension, or a folder
* name without an extension.
* @param {SpecifierSource} importee source like '@lion/core' or '../helpers/index.js'
* @param {PathFromSystemRoot} importer importing file, like '/my/project/importing-file.js'
* @param {{customResolveOptions?: {preserveSymlinks:boolean}}} [opts] nodeResolve options
* @returns {Promise<PathFromSystemRoot|null>} the resolved file system path, like '/my/project/node_modules/@lion/core/index.js'
*/
async function resolveImportPathFn(importee, importer, opts) {
const rollupResolve = nodeResolve({ const rollupResolve = nodeResolve({
rootDir: pathLib.dirname(importer), rootDir: pathLib.dirname(importer),
// allow resolving polyfills for nodejs libs // allow resolving polyfills for nodejs libs
@ -59,15 +68,4 @@ async function resolveImportPath(importee, importer, opts) {
return toPosixPath(result.id); return toPosixPath(result.id);
} }
/** export const resolveImportPath = memoize(resolveImportPathFn);
* Based on importee (in a statement "import {x} from '@lion/core'", "@lion/core" is an
* importee), which can be a bare module specifier, a filename without extension, or a folder
* name without an extension.
* @param {SpecifierSource} importee source like '@lion/core' or '../helpers/index.js'
* @param {PathFromSystemRoot} importer importing file, like '/my/project/importing-file.js'
* @param {{customResolveOptions?: {preserveSymlinks:boolean}}} [opts] nodeResolve options
* @returns {Promise<PathFromSystemRoot|null>} the resolved file system path, like '/my/project/node_modules/@lion/core/index.js'
*/
const resolveImportPathMemoized = memoize(resolveImportPath);
module.exports = { resolveImportPath: resolveImportPathMemoized };

View file

@ -1,16 +1,14 @@
/** /**
* @typedef {import('../types/core/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
*/ */
/** /**
* @param {PathFromSystemRoot|string} pathStr C:\Example\path/like/this * @param {PathFromSystemRoot|string} pathStr C:\Example\path/like/this
* @returns {PathFromSystemRoot} /Example/path/like/this * @returns {PathFromSystemRoot} /Example/path/like/this
*/ */
function toPosixPath(pathStr) { export function toPosixPath(pathStr) {
if (process.platform === 'win32') { if (process.platform === 'win32') {
return /** @type {PathFromSystemRoot} */ (pathStr.replace(/^.:/, '').replace(/\\/g, '/')); return /** @type {PathFromSystemRoot} */ (pathStr.replace(/^.:/, '').replace(/\\/g, '/'));
} }
return /** @type {PathFromSystemRoot} */ (pathStr); return /** @type {PathFromSystemRoot} */ (pathStr);
} }
module.exports = { toPosixPath };

View file

@ -1,9 +1,8 @@
/** /**
* @param {ASTNode} curNode Node to start from. Will loop over its children * @param {Node} curNode Node to start from. Will loop over its children
* @param {object} processObject Will be executed for every node * @param {object} processObject Will be executed for every node
* @param {ASTNode} [parentNode] parent of curNode
*/ */
function traverseHtml(curNode, processObject) { export function traverseHtml(curNode, processObject) {
function pathify(node) { function pathify(node) {
return { return {
node, node,
@ -24,5 +23,3 @@ function traverseHtml(curNode, processObject) {
}); });
} }
} }
module.exports = traverseHtml;

View file

@ -1,13 +1,13 @@
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
// @ts-ignore // @ts-expect-error
const mockFs = require('mock-fs'); import mockFs from 'mock-fs';
// @ts-ignore // @ts-expect-error
const mockRequire = require('mock-require'); import mockRequire from 'mock-require';
/** /**
* @param {object} obj * @param {object} obj
*/ */
function mockFsAndRequire(obj) { export function mockFsAndRequire(obj) {
mockFs(obj); mockFs(obj);
// Object.entries(obj).forEach(([key, value]) => { // Object.entries(obj).forEach(([key, value]) => {
@ -23,7 +23,3 @@ mockFsAndRequire.restore = () => {
mockFs.restore(); mockFs.restore();
mockRequire.stopAll(); mockRequire.stopAll();
}; };
module.exports = {
mockFsAndRequire,
};

View file

@ -1,57 +1,47 @@
const { LogService } = require('../src/program/core/LogService.js'); import { LogService } from '../src/program/core/LogService.js';
const originalWarn = LogService.warn; const originalWarn = LogService.warn;
function suppressWarningLogs() { export function suppressWarningLogs() {
LogService.warn = () => {}; LogService.warn = () => {};
} }
function restoreSuppressWarningLogs() { export function restoreSuppressWarningLogs() {
LogService.warn = originalWarn; LogService.warn = originalWarn;
} }
const originalInfo = LogService.info; const originalInfo = LogService.info;
function suppressInfoLogs() { export function suppressInfoLogs() {
LogService.info = () => {}; LogService.info = () => {};
} }
function restoreSuppressInfoLogs() { export function restoreSuppressInfoLogs() {
LogService.info = originalInfo; LogService.info = originalInfo;
} }
const originalDebug = LogService.debug; const originalDebug = LogService.debug;
function suppressDebugLogs() { export function suppressDebugLogs() {
LogService.debug = () => {}; LogService.debug = () => {};
} }
function restoreSuppressDebugLogs() { export function restoreSuppressDebugLogs() {
LogService.debug = originalDebug; LogService.debug = originalDebug;
} }
const originalSuccess = LogService.success; const originalSuccess = LogService.success;
function suppressSuccessLogs() { export function suppressSuccessLogs() {
LogService.success = () => {}; LogService.success = () => {};
} }
function restoreSuppressSuccessLogs() { export function restoreSuppressSuccessLogs() {
LogService.success = originalSuccess; LogService.success = originalSuccess;
} }
function suppressNonCriticalLogs() { export function suppressNonCriticalLogs() {
suppressInfoLogs(); suppressInfoLogs();
suppressWarningLogs(); suppressWarningLogs();
suppressDebugLogs(); suppressDebugLogs();
suppressSuccessLogs(); suppressSuccessLogs();
} }
function restoreSuppressNonCriticalLogs() { export function restoreSuppressNonCriticalLogs() {
restoreSuppressInfoLogs(); restoreSuppressInfoLogs();
restoreSuppressWarningLogs(); restoreSuppressWarningLogs();
restoreSuppressDebugLogs(); restoreSuppressDebugLogs();
restoreSuppressSuccessLogs(); restoreSuppressSuccessLogs();
} }
module.exports = {
suppressWarningLogs,
restoreSuppressWarningLogs,
suppressInfoLogs,
restoreSuppressInfoLogs,
suppressNonCriticalLogs,
restoreSuppressNonCriticalLogs,
};

View file

@ -1,8 +1,8 @@
const path = require('path'); import path from 'path';
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
const { mockFsAndRequire: mock } = require('./mock-fs-and-require.js'); import { mockFsAndRequire } from './mock-fs-and-require.js';
export const mock = mockFsAndRequire;
/** /**
* Makes sure that, whenever the main program (providence) calls * Makes sure that, whenever the main program (providence) calls
@ -70,21 +70,21 @@ function getMockObjectForProject(files, cfg = {}, existingMock = {}) {
* paths match with the indexes of the files * paths match with the indexes of the files
* @param {object} existingMock config for mock-fs, so the previous config is not overridden * @param {object} existingMock config for mock-fs, so the previous config is not overridden
*/ */
function mockProject(files, cfg = {}, existingMock = {}) { export function mockProject(files, cfg = {}, existingMock = {}) {
const obj = getMockObjectForProject(files, cfg, existingMock); const obj = getMockObjectForProject(files, cfg, existingMock);
mock(obj); mockFsAndRequire(obj);
return obj; return obj;
} }
function restoreMockedProjects() { export function restoreMockedProjects() {
mock.restore(); mockFsAndRequire.restore();
} }
function getEntry(queryResult, index = 0) { export function getEntry(queryResult, index = 0) {
return queryResult.queryOutput[index]; return queryResult.queryOutput[index];
} }
function getEntries(queryResult) { export function getEntries(queryResult) {
return queryResult.queryOutput; return queryResult.queryOutput;
} }
@ -113,7 +113,7 @@ function createPackageJson({ filePaths, codeSnippets, projectName, refProjectNam
* When a non imported ref dependency or a wrong version of a dev dependency needs to be * When a non imported ref dependency or a wrong version of a dev dependency needs to be
* tested, please explicitly provide a ./package.json that does so. * tested, please explicitly provide a ./package.json that does so.
*/ */
function mockTargetAndReferenceProject(searchTargetProject, referenceProject) { export function mockTargetAndReferenceProject(searchTargetProject, referenceProject) {
const targetProjectName = searchTargetProject.name || 'fictional-target-project'; const targetProjectName = searchTargetProject.name || 'fictional-target-project';
const refProjectName = referenceProject.name || 'fictional-ref-project'; const refProjectName = referenceProject.name || 'fictional-ref-project';
@ -156,12 +156,3 @@ function mockTargetAndReferenceProject(searchTargetProject, referenceProject) {
targetMock, targetMock,
); );
} }
module.exports = {
mock,
mockProject,
restoreMockedProjects,
getEntry,
getEntries,
mockTargetAndReferenceProject,
};

View file

@ -1,31 +0,0 @@
const { ReportService } = require('../src/program/core/ReportService.js');
/**
* @typedef {import('../src/program/types/core').QueryResult} QueryResult
*/
const originalWriteToJson = ReportService.writeToJson;
/**
* @param {QueryResult[]} queryResults
*/
function mockWriteToJson(queryResults) {
ReportService.writeToJson = queryResult => {
queryResults.push(queryResult);
};
}
/**
* @param {QueryResult[]} [queryResults]
*/
function restoreWriteToJson(queryResults) {
ReportService.writeToJson = originalWriteToJson;
while (queryResults?.length) {
queryResults.pop();
}
}
module.exports = {
mockWriteToJson,
restoreWriteToJson,
};

View file

@ -1,4 +1,5 @@
Has a deprecated (from Node 16) export maps format: Has a deprecated (from Node 16) export maps format:
``` ```
"exports": { "exports": {
"./src/": "./src/" "./src/": "./src/"

View file

@ -1,21 +1,22 @@
const { InputDataService } = require('../src/program/core/InputDataService.js'); import { InputDataService } from '../src/program/core/InputDataService.js';
const { QueryService } = require('../src/program/core/QueryService.js'); import { QueryService } from '../src/program/core/QueryService.js';
const { restoreMockedProjects } = require('./mock-project-helpers.js'); import { restoreMockedProjects } from './mock-project-helpers.js';
const { mockWriteToJson, restoreWriteToJson } = require('./mock-report-service-helpers.js'); import {
const {
suppressNonCriticalLogs, suppressNonCriticalLogs,
restoreSuppressNonCriticalLogs, restoreSuppressNonCriticalLogs,
} = require('./mock-log-service-helpers.js'); } from './mock-log-service-helpers.js';
const { memoizeConfig } = require('../src/program/utils/memoize.js'); import { memoizeConfig } from '../src/program/utils/memoize.js';
/** /**
* @typedef {import('../src/program/types/core').QueryResult} QueryResult * @typedef {import('../types/index.js').QueryResult} QueryResult
* @returns {QueryResult[]}
*/ */
function setupAnalyzerTest() { let hasRunBefore = false;
/** @type {QueryResult[]} */
const queryResults = []; export function setupAnalyzerTest() {
if (hasRunBefore) {
return;
}
const originalReferenceProjectPaths = InputDataService.referenceProjectPaths; const originalReferenceProjectPaths = InputDataService.referenceProjectPaths;
const cacheDisabledQInitialValue = QueryService.cacheDisabled; const cacheDisabledQInitialValue = QueryService.cacheDisabled;
@ -35,16 +36,12 @@ function setupAnalyzerTest() {
beforeEach(() => { beforeEach(() => {
InputDataService.referenceProjectPaths = []; InputDataService.referenceProjectPaths = [];
mockWriteToJson(queryResults);
}); });
afterEach(() => { afterEach(() => {
InputDataService.referenceProjectPaths = originalReferenceProjectPaths; InputDataService.referenceProjectPaths = originalReferenceProjectPaths;
restoreWriteToJson(queryResults);
restoreMockedProjects(); restoreMockedProjects();
}); });
return queryResults; hasRunBefore = true;
} }
module.exports = { setupAnalyzerTest };

View file

@ -1,8 +1,9 @@
const { Analyzer } = require('../../src/program/core/Analyzer.js'); import { Analyzer } from '../../src/program/core/Analyzer.js';
/** /**
* @typedef {import('@babel/types').File} File * @typedef {import('@babel/types').File} File
* @typedef {import('../../src/program/types/core').QueryOutputEntry} QueryOutputEntry * @typedef {import('../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('../../types/index.js').QueryOutputEntry} QueryOutputEntry
*/ */
/** /**
@ -31,7 +32,8 @@ const options = {
* @param {File} ast * @param {File} ast
*/ */
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
function myAnalyzerPerAstEntry(ast) { function getResultPerAstFile(ast) {
console.debug('myAnalyzerPerAstEntry');
// Visit AST... // Visit AST...
const transformedEntryResult = []; const transformedEntryResult = [];
// Do the traverse: https://babeljs.io/docs/en/babel-traverse // Do the traverse: https://babeljs.io/docs/en/babel-traverse
@ -40,10 +42,9 @@ function myAnalyzerPerAstEntry(ast) {
return transformedEntryResult; return transformedEntryResult;
} }
class DummyAnalyzer extends Analyzer { export class DummyAnalyzer extends Analyzer {
static get analyzerName() { /** @type {AnalyzerName} */
return 'dummy-analyzer'; static analyzerName = 'find-dummy-analyzer';
}
/** /**
* @param {AstDataProject[]} astDataProjects * @param {AstDataProject[]} astDataProjects
@ -71,7 +72,7 @@ class DummyAnalyzer extends Analyzer {
*/ */
const queryOutput = await this._traverse((ast, astContext) => { const queryOutput = await this._traverse((ast, astContext) => {
// Run the traversel per entry // Run the traversel per entry
let transformedEntryResult = myAnalyzerPerAstEntry(ast); let transformedEntryResult = getResultPerAstFile(ast);
const meta = {}; const meta = {};
// (optional): Post processors on TransformedEntry // (optional): Post processors on TransformedEntry
@ -93,5 +94,3 @@ class DummyAnalyzer extends Analyzer {
return this._finalize(queryOutput, cfg); return this._finalize(queryOutput, cfg);
} }
} }
module.exports = { DummyAnalyzer };

View file

@ -0,0 +1,415 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable import/no-extraneous-dependencies */
import sinon from 'sinon';
import pathLib from 'path';
import { fileURLToPath } from 'url';
import { expect } from 'chai';
import { it } from 'mocha';
import {
mockProject,
restoreMockedProjects,
mockTargetAndReferenceProject,
} from '../../test-helpers/mock-project-helpers.js';
import { _providenceModule } from '../../src/program/providence.js';
import { _cliHelpersModule } from '../../src/cli/cli-helpers.js';
import { toPosixPath } from '../../src/program/utils/to-posix-path.js';
import { memoizeConfig } from '../../src/program/utils/memoize.js';
import { getExtendDocsResults } from '../../src/cli/launch-providence-with-extend-docs.js';
import { AstService } from '../../src/index.js';
import { setupAnalyzerTest } from '../../test-helpers/setup-analyzer-test.js';
/**
* @typedef {import('../../types/index.js').QueryResult} QueryResult
*/
const __dirname = pathLib.dirname(fileURLToPath(import.meta.url));
const { pathsArrayFromCs, pathsArrayFromCollectionName, appendProjectDependencyPaths } =
_cliHelpersModule;
const externalCfgMock = {
searchTargetCollections: {
'lion-collection': [
'./providence-input-data/search-targets/example-project-a',
'./providence-input-data/search-targets/example-project-b',
// ...etc
],
},
referenceCollections: {
'lion-based-ui-collection': [
'./providence-input-data/references/lion-based-ui',
'./providence-input-data/references/lion-based-ui-labs',
],
},
};
setupAnalyzerTest();
describe('CLI helpers', () => {
const rootDir = toPosixPath(pathLib.resolve(__dirname, '../../'));
describe('pathsArrayFromCs', () => {
it('allows absolute paths', async () => {
expect(pathsArrayFromCs('/mocked/path/example-project', rootDir)).to.eql([
'/mocked/path/example-project',
]);
});
it('allows relative paths', async () => {
expect(
pathsArrayFromCs('./test-helpers/project-mocks/importing-target-project', rootDir),
).to.eql([`${rootDir}/test-helpers/project-mocks/importing-target-project`]);
expect(
pathsArrayFromCs('test-helpers/project-mocks/importing-target-project', rootDir),
).to.eql([`${rootDir}/test-helpers/project-mocks/importing-target-project`]);
});
it('allows globs', async () => {
expect(pathsArrayFromCs('test-helpers/project-mocks*', rootDir)).to.eql([
`${rootDir}/test-helpers/project-mocks`,
`${rootDir}/test-helpers/project-mocks-analyzer-outputs`,
]);
});
it('allows multiple comma separated paths', async () => {
const paths =
'test-helpers/project-mocks*, ./test-helpers/project-mocks/importing-target-project,/mocked/path/example-project';
expect(pathsArrayFromCs(paths, rootDir)).to.eql([
`${rootDir}/test-helpers/project-mocks`,
`${rootDir}/test-helpers/project-mocks-analyzer-outputs`,
`${rootDir}/test-helpers/project-mocks/importing-target-project`,
'/mocked/path/example-project',
]);
});
});
describe('pathsArrayFromCollectionName', () => {
it('gets collections from external target config', async () => {
expect(
pathsArrayFromCollectionName('lion-collection', 'search-target', externalCfgMock, rootDir),
).to.eql(
externalCfgMock.searchTargetCollections['lion-collection'].map(p =>
toPosixPath(pathLib.join(rootDir, p)),
),
);
});
it('gets collections from external reference config', async () => {
expect(
pathsArrayFromCollectionName(
'lion-based-ui-collection',
'reference',
externalCfgMock,
rootDir,
),
).to.eql(
externalCfgMock.referenceCollections['lion-based-ui-collection'].map(p =>
toPosixPath(pathLib.join(rootDir, p)),
),
);
});
});
describe('appendProjectDependencyPaths', () => {
beforeEach(() => {
mockProject(
{
'./src/OriginalComp.js': `export class OriginalComp {}`,
'./src/inbetween.js': `export { OriginalComp as InBetweenComp } from './OriginalComp.js'`,
'./index.js': `export { InBetweenComp as MyComp } from './src/inbetween.js'`,
'./node_modules/dependency-a/index.js': '',
'./bower_components/dependency-b/index.js': '',
'./node_modules/my-dependency/index.js': '',
},
{
projectName: 'example-project',
projectPath: '/mocked/path/example-project',
},
);
});
it('adds bower and node dependencies', async () => {
const result = await appendProjectDependencyPaths(['/mocked/path/example-project']);
expect(result).to.eql([
'/mocked/path/example-project/node_modules/dependency-a',
'/mocked/path/example-project/node_modules/my-dependency',
'/mocked/path/example-project/bower_components/dependency-b',
'/mocked/path/example-project',
]);
});
it('allows a regex filter', async () => {
const result = await appendProjectDependencyPaths(
['/mocked/path/example-project'],
'/^dependency-/',
);
expect(result).to.eql([
'/mocked/path/example-project/node_modules/dependency-a',
// in windows, it should not add '/mocked/path/example-project/node_modules/my-dependency',
'/mocked/path/example-project/bower_components/dependency-b',
'/mocked/path/example-project',
]);
const result2 = await appendProjectDependencyPaths(['/mocked/path/example-project'], '/b$/');
expect(result2).to.eql([
'/mocked/path/example-project/bower_components/dependency-b',
'/mocked/path/example-project',
]);
});
it('allows to filter out only npm or bower deps', async () => {
const result = await appendProjectDependencyPaths(
['/mocked/path/example-project'],
undefined,
['npm'],
);
expect(result).to.eql([
'/mocked/path/example-project/node_modules/dependency-a',
'/mocked/path/example-project/node_modules/my-dependency',
'/mocked/path/example-project',
]);
const result2 = await appendProjectDependencyPaths(
['/mocked/path/example-project'],
undefined,
['bower'],
);
expect(result2).to.eql([
'/mocked/path/example-project/bower_components/dependency-b',
'/mocked/path/example-project',
]);
});
});
describe('Extend docs', () => {
afterEach(() => {
restoreMockedProjects();
});
it('rewrites monorepo package paths when analysis is run from monorepo root', async () => {
// This fails after InputDataService.addAstToProjectsData is memoized
// (it does pass when run in isolation however, as a quick fix we disable memoization cache here...)
memoizeConfig.isCacheDisabled = true;
// Since we use the print method here, we need to force Babel, bc swc-to-babel output is not compatible
// with @babel/generate
const initialAstServiceFallbackToBabel = AstService.fallbackToBabel;
AstService.fallbackToBabel = true;
const theirProjectFiles = {
'./package.json': JSON.stringify({
name: 'their-components',
version: '1.0.0',
}),
'./src/TheirButton.js': `export class TheirButton extends HTMLElement {}`,
'./src/TheirTooltip.js': `export class TheirTooltip extends HTMLElement {}`,
'./their-button.js': `
import { TheirButton } from './src/TheirButton.js';
customElements.define('their-button', TheirButton);
`,
'./demo.js': `
import { TheirTooltip } from './src/TheirTooltip.js';
import './their-button.js';
`,
};
const myProjectFiles = {
'./package.json': JSON.stringify({
name: '@my/root',
workspaces: ['packages/*', 'another-folder/my-tooltip'],
dependencies: {
'their-components': '1.0.0',
},
}),
// Package 1: @my/button
'./packages/button/package.json': JSON.stringify({
name: '@my/button',
}),
'./packages/button/src/MyButton.js': `
import { TheirButton } from 'their-components/src/TheirButton.js';
export class MyButton extends TheirButton {}
`,
'./packages/button/src/my-button.js': `
import { MyButton } from './MyButton.js';
customElements.define('my-button', MyButton);
`,
// Package 2: @my/tooltip
'./packages/tooltip/package.json': JSON.stringify({
name: '@my/tooltip',
}),
'./packages/tooltip/src/MyTooltip.js': `
import { TheirTooltip } from 'their-components/src/TheirTooltip.js';
export class MyTooltip extends TheirTooltip {}
`,
};
const theirProject = {
path: '/my-components/node_modules/their-components',
name: 'their-components',
files: Object.entries(theirProjectFiles).map(([file, code]) => ({ file, code })),
};
const myProject = {
path: '/my-components',
name: 'my-components',
files: Object.entries(myProjectFiles).map(([file, code]) => ({ file, code })),
};
mockTargetAndReferenceProject(theirProject, myProject);
const result = await getExtendDocsResults({
referenceProjectPaths: [theirProject.path],
prefixCfg: { from: 'their', to: 'my' },
extensions: ['.js'],
cwd: '/my-components',
});
expect(result).to.eql([
{
name: 'TheirButton',
variable: {
from: 'TheirButton',
to: 'MyButton',
paths: [
{
from: './src/TheirButton.js',
to: '@my/button/src/MyButton.js', // rewritten from './packages/button/src/MyButton.js',
},
{
from: 'their-components/src/TheirButton.js',
to: '@my/button/src/MyButton.js', // rewritten from './packages/button/src/MyButton.js',
},
],
},
tag: {
from: 'their-button',
to: 'my-button',
paths: [
{
from: './their-button.js',
to: '@my/button/src/my-button.js', // rewritten from './packages/button/src/MyButton.js',
},
{
from: 'their-components/their-button.js',
to: '@my/button/src/my-button.js', // rewritten from './packages/button/src/MyButton.js',
},
],
},
},
{
name: 'TheirTooltip',
variable: {
from: 'TheirTooltip',
to: 'MyTooltip',
paths: [
{
from: './src/TheirTooltip.js',
to: '@my/tooltip/src/MyTooltip.js', // './packages/tooltip/src/MyTooltip.js',
},
{
from: 'their-components/src/TheirTooltip.js',
to: '@my/tooltip/src/MyTooltip.js', // './packages/tooltip/src/MyTooltip.js',
},
],
},
},
]);
AstService.fallbackToBabel = initialAstServiceFallbackToBabel;
});
it('does not check for match compatibility (target and reference) in monorepo targets', async () => {
// ===== REFERENCE AND TARGET PROJECTS =====
const theirProjectFiles = {
'./package.json': JSON.stringify({
name: 'their-components',
version: '1.0.0',
}),
'./src/TheirButton.js': `export class TheirButton extends HTMLElement {}`,
};
// This will be detected as being a monorepo
const monoProjectFiles = {
'./package.json': JSON.stringify({
name: '@mono/root',
workspaces: ['packages/*'],
dependencies: {
'their-components': '1.0.0',
},
}),
// Package: @mono/button
'./packages/button/package.json': JSON.stringify({
name: '@mono/button',
}),
};
// This will be detected as NOT being a monorepo
const nonMonoProjectFiles = {
'./package.json': JSON.stringify({
name: 'non-mono',
dependencies: {
'their-components': '1.0.0',
},
}),
};
const theirProject = {
path: '/their-components',
name: 'their-components',
files: Object.entries(theirProjectFiles).map(([file, code]) => ({ file, code })),
};
const monoProject = {
path: '/mono-components',
name: 'mono-components',
files: Object.entries(monoProjectFiles).map(([file, code]) => ({ file, code })),
};
const nonMonoProject = {
path: '/non-mono-components',
name: 'non-mono-components',
files: Object.entries(nonMonoProjectFiles).map(([file, code]) => ({ file, code })),
};
// ===== TESTS =====
const providenceStub = sinon.stub(_providenceModule, 'providence').returns(
new Promise(resolve => {
resolve([]);
}),
);
// ===== mono =====
mockTargetAndReferenceProject(theirProject, monoProject);
await getExtendDocsResults({
referenceProjectPaths: ['/their-components'],
prefixCfg: { from: 'their', to: 'my' },
extensions: ['.js'],
cwd: '/mono-components',
});
expect(providenceStub.args[0][1].skipCheckMatchCompatibility).to.equal(true);
providenceStub.resetHistory();
restoreMockedProjects();
// ===== non mono =====
mockTargetAndReferenceProject(theirProject, nonMonoProject);
await getExtendDocsResults({
referenceProjectPaths: ['/their-components'],
prefixCfg: { from: 'their', to: 'my' },
extensions: ['.js'],
cwd: '/non-mono-components',
});
expect(providenceStub.args[0][1].skipCheckMatchCompatibility).to.equal(false);
providenceStub.restore();
});
});
});

View file

@ -1,9 +1,10 @@
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import pathLib from 'path'; import pathLib from 'path';
import { expect } from 'chai'; import { expect } from 'chai';
import { it } from 'mocha';
import { appendProjectDependencyPaths } from '../../src/cli/cli-helpers.js'; import { appendProjectDependencyPaths } from '../../src/cli/cli-helpers.js';
import { toPosixPath } from '../../src/program/utils/to-posix-path.js'; import { toPosixPath } from '../../src/program/utils/to-posix-path.js';
import { getCurrentDir } from '../../src/program/utils/get-current-dir.mjs'; import { getCurrentDir } from '../../src/program/utils/get-current-dir.js';
/** /**
* These tests are added on top of unit tests. See: * These tests are added on top of unit tests. See:

View file

@ -0,0 +1,436 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable import/no-extraneous-dependencies */
import sinon from 'sinon';
import { expect } from 'chai';
import { it } from 'mocha';
import commander from 'commander';
import { mockProject } from '../../test-helpers/mock-project-helpers.js';
import { InputDataService } from '../../src/program/core/InputDataService.js';
import { QueryService } from '../../src/program/core/QueryService.js';
import { _providenceModule } from '../../src/program/providence.js';
import { _cliHelpersModule } from '../../src/cli/cli-helpers.js';
import { cli } from '../../src/cli/cli.js';
import { _promptAnalyzerMenuModule } from '../../src/cli/prompt-analyzer-menu.js';
import { memoizeConfig } from '../../src/program/utils/memoize.js';
import { _extendDocsModule } from '../../src/cli/launch-providence-with-extend-docs.js';
import { dashboardServer } from '../../dashboard/server.js';
import { setupAnalyzerTest } from '../../test-helpers/setup-analyzer-test.js';
/**
* @typedef {import('../../types/index.js').QueryResult} QueryResult
*/
const externalCfgMock = {
searchTargetCollections: {
'lion-collection': [
'./providence-input-data/search-targets/example-project-a',
'./providence-input-data/search-targets/example-project-b',
// ...etc
],
},
referenceCollections: {
'lion-based-ui-collection': [
'./providence-input-data/references/lion-based-ui',
'./providence-input-data/references/lion-based-ui-labs',
],
},
};
setupAnalyzerTest();
/**
* @param {string} args
* @param {string} cwd
*/
async function runCli(args, cwd) {
const argv = [
...process.argv.slice(0, 2),
...args.split(' ').map(a => a.replace(/^("|')?(.*)("|')?$/, '$2')),
];
await cli({ argv, cwd });
}
describe('Providence CLI', () => {
const rootDir = '/mocked/path/example-project';
/** @type {sinon.SinonStub} */
let providenceStub;
/** @type {sinon.SinonStub} */
let promptCfgStub;
/** @type {sinon.SinonStub} */
let iExtConfStub;
/** @type {sinon.SinonStub} */
let promptStub;
/** @type {sinon.SinonStub} */
let qConfStub;
before(() => {
// Prevent MaxListenersExceededWarning
commander.setMaxListeners(100);
/** @type {sinon.SinonStub} */
providenceStub = sinon.stub(_providenceModule, 'providence').returns(Promise.resolve());
/** @type {sinon.SinonStub} */
promptCfgStub = sinon
.stub(_promptAnalyzerMenuModule, 'promptAnalyzerConfigMenu')
.returns(Promise.resolve({ analyzerConfig: { con: 'fig' } }));
/** @type {sinon.SinonStub} */
iExtConfStub = sinon.stub(InputDataService, 'getExternalConfig').returns(externalCfgMock);
/** @type {sinon.SinonStub} */
promptStub = sinon
.stub(_promptAnalyzerMenuModule, 'promptAnalyzerMenu')
.returns(Promise.resolve({ analyzerName: 'match-analyzer-mock' }));
/** @type {sinon.SinonStub} */
qConfStub = sinon.stub(QueryService, 'getQueryConfigFromAnalyzer').returns(
// @ts-expect-error
Promise.resolve({
analyzer: {
name: 'match-analyzer-mock',
requiresReference: true,
},
}),
);
});
after(() => {
commander.setMaxListeners(10);
providenceStub.restore();
promptCfgStub.restore();
iExtConfStub.restore();
promptStub.restore();
qConfStub.restore();
});
beforeEach(() => {
mockProject(
{
'./src/OriginalComp.js': `export class OriginalComp {}`,
'./src/inbetween.js': `export { OriginalComp as InBetweenComp } from './OriginalComp.js'`,
'./index.js': `export { InBetweenComp as MyComp } from './src/inbetween.js'`,
'./node_modules/dependency-a/index.js': '',
'./bower_components/dependency-b/index.js': '',
},
{
projectName: 'example-project',
projectPath: '/mocked/path/example-project',
},
);
memoizeConfig.isCacheDisabled = true;
});
afterEach(() => {
providenceStub.resetHistory();
promptCfgStub.resetHistory();
iExtConfStub.resetHistory();
promptStub.resetHistory();
qConfStub.resetHistory();
});
const analyzeCmd = 'analyze match-analyzer-mock';
it('calls providence', async () => {
await runCli(`${analyzeCmd} -t /mocked/path/example-project`, rootDir);
expect(providenceStub.called).to.be.true;
});
it('creates a QueryConfig', async () => {
await runCli(`${analyzeCmd} -t /mocked/path/example-project`, rootDir);
expect(qConfStub.called).to.be.true;
expect(qConfStub.args[0][0]).to.equal('match-analyzer-mock');
});
describe('Global options', () => {
const anyCmdThatAcceptsGlobalOpts = 'analyze match-analyzer-mock';
/** @type {sinon.SinonStub} */
let pathsArrayFromCollectionStub;
/** @type {sinon.SinonStub} */
let pathsArrayFromCsStub;
/** @type {sinon.SinonStub} */
let appendProjectDependencyPathsStub;
before(() => {
pathsArrayFromCsStub = sinon
.stub(_cliHelpersModule, 'pathsArrayFromCs')
.returns(['/mocked/path/example-project']);
pathsArrayFromCollectionStub = sinon
.stub(_cliHelpersModule, 'pathsArrayFromCollectionName')
.returns(['/mocked/path/example-project']);
appendProjectDependencyPathsStub = sinon
.stub(_cliHelpersModule, 'appendProjectDependencyPaths')
.returns(
Promise.resolve([
'/mocked/path/example-project',
'/mocked/path/example-project/node_modules/mock-dep-a',
'/mocked/path/example-project/bower_components/mock-dep-b',
]),
);
});
after(() => {
pathsArrayFromCsStub.restore();
pathsArrayFromCollectionStub.restore();
appendProjectDependencyPathsStub.restore();
});
afterEach(() => {
pathsArrayFromCsStub.resetHistory();
pathsArrayFromCollectionStub.resetHistory();
appendProjectDependencyPathsStub.resetHistory();
});
it('"-e --extensions"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts} -e bla,blu`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfig.extensions).to.eql(['.bla', '.blu']);
providenceStub.resetHistory();
await runCli(`${anyCmdThatAcceptsGlobalOpts} --extensions bla,blu`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfig.extensions).to.eql(['.bla', '.blu']);
});
it('"-t --search-target-paths"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts} -t /mocked/path/example-project`, rootDir);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].targetProjectPaths).to.eql(['/mocked/path/example-project']);
pathsArrayFromCsStub.resetHistory();
providenceStub.resetHistory();
await runCli(
`${anyCmdThatAcceptsGlobalOpts} --search-target-paths /mocked/path/example-project`,
rootDir,
);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].targetProjectPaths).to.eql(['/mocked/path/example-project']);
});
it('"-r --reference-paths"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts} -r /mocked/path/example-project`, rootDir);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].referenceProjectPaths).to.eql([
'/mocked/path/example-project',
]);
pathsArrayFromCsStub.resetHistory();
providenceStub.resetHistory();
await runCli(
`${anyCmdThatAcceptsGlobalOpts} --reference-paths /mocked/path/example-project`,
rootDir,
);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].referenceProjectPaths).to.eql([
'/mocked/path/example-project',
]);
});
it('"--search-target-collection"', async () => {
await runCli(
`${anyCmdThatAcceptsGlobalOpts} --search-target-collection lion-collection`,
rootDir,
);
expect(pathsArrayFromCollectionStub.args[0][0]).to.equal('lion-collection');
expect(providenceStub.args[0][1].targetProjectPaths).to.eql(['/mocked/path/example-project']);
});
it('"--reference-collection"', async () => {
await runCli(
`${anyCmdThatAcceptsGlobalOpts} --reference-collection lion-based-ui-collection`,
rootDir,
);
expect(pathsArrayFromCollectionStub.args[0][0]).to.equal('lion-based-ui-collection');
expect(providenceStub.args[0][1].referenceProjectPaths).to.eql([
'/mocked/path/example-project',
]);
});
it('"-a --allowlist"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts} -a mocked/**/*,rocked/*`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfig.allowlist).to.eql([
'mocked/**/*',
'rocked/*',
]);
providenceStub.resetHistory();
await runCli(`${anyCmdThatAcceptsGlobalOpts} --allowlist mocked/**/*,rocked/*`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfig.allowlist).to.eql([
'mocked/**/*',
'rocked/*',
]);
});
it('"--allowlist-reference"', async () => {
await runCli(
`${anyCmdThatAcceptsGlobalOpts} --allowlist-reference mocked/**/*,rocked/*`,
rootDir,
);
expect(providenceStub.args[0][1].gatherFilesConfigReference.allowlist).to.eql([
'mocked/**/*',
'rocked/*',
]);
});
it('"--allowlist-mode"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts} --allowlist-mode git`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfig.allowlistMode).to.equal('git');
});
it('"--allowlist-mode-reference"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts} --allowlist-mode-reference npm`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfigReference.allowlistMode).to.equal('npm');
});
it('"-D --debug"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts} -D`, rootDir);
expect(providenceStub.args[0][1].debugEnabled).to.equal(true);
providenceStub.resetHistory();
await runCli(`${anyCmdThatAcceptsGlobalOpts} --debug`, rootDir);
expect(providenceStub.args[0][1].debugEnabled).to.equal(true);
});
it('"--write-log-file"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts} --write-log-file`, rootDir);
expect(providenceStub.args[0][1].writeLogFile).to.equal(true);
});
it('"--target-dependencies"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts}`, rootDir);
expect(appendProjectDependencyPathsStub.called).to.be.false;
appendProjectDependencyPathsStub.resetHistory();
providenceStub.resetHistory();
await runCli(`${anyCmdThatAcceptsGlobalOpts} --target-dependencies`, rootDir);
expect(appendProjectDependencyPathsStub.called).to.be.true;
expect(providenceStub.args[0][1].targetProjectPaths).to.eql([
'/mocked/path/example-project',
'/mocked/path/example-project/node_modules/mock-dep-a',
'/mocked/path/example-project/bower_components/mock-dep-b',
]);
});
it('"--target-dependencies /^with-regex/"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts} --target-dependencies /^mock-/`, rootDir);
expect(appendProjectDependencyPathsStub.args[0][1]).to.equal('/^mock-/');
});
it('"--skip-check-match-compatibility"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts} --skip-check-match-compatibility`, rootDir);
expect(providenceStub.args[0][1].skipCheckMatchCompatibility).to.equal(true);
});
it('"--fallback-to-babel"', async () => {
await runCli(`${anyCmdThatAcceptsGlobalOpts} --fallback-to-babel`, rootDir);
expect(providenceStub.args[0][1].fallbackToBabel).to.equal(true);
});
});
describe('Commands', () => {
describe('Analyze', () => {
it('calls providence', async () => {
await runCli(`${analyzeCmd}`, rootDir);
expect(providenceStub.called).to.be.true;
});
describe('Options', () => {
it('"-o --prompt-optional-config"', async () => {
await runCli(`analyze -o`, rootDir);
expect(promptStub.called).to.be.true;
promptStub.resetHistory();
await runCli(`analyze --prompt-optional-config`, rootDir);
expect(promptStub.called).to.be.true;
});
it('"-c --config"', async () => {
await runCli(`analyze match-analyzer-mock -c {"a":"2"}`, rootDir);
expect(qConfStub.args[0][0]).to.equal('match-analyzer-mock');
expect(qConfStub.args[0][1]).to.eql({ a: '2', metaConfig: {} });
qConfStub.resetHistory();
await runCli(`analyze match-analyzer-mock --config {"a":"2"}`, rootDir);
expect(qConfStub.args[0][0]).to.equal('match-analyzer-mock');
expect(qConfStub.args[0][1]).to.eql({ a: '2', metaConfig: {} });
});
it('calls "promptAnalyzerConfigMenu" without config given', async () => {
await runCli(`analyze match-analyzer-mock`, rootDir);
expect(promptCfgStub.called).to.be.true;
});
});
});
describe.skip('Query', () => {});
describe.skip('Search', () => {});
describe('Manage', () => {});
describe('Dashboard', () => {
/** @type {sinon.SinonStub} */
const startStub = sinon.stub(dashboardServer, 'start');
it('spawns a dashboard', async () => {
runCli(`dashboard`, rootDir);
expect(startStub.called).to.be.true;
});
});
describe('Extend docs', () => {
/** @type {sinon.SinonStub} */
let extendDocsStub;
before(() => {
extendDocsStub = sinon
.stub(_extendDocsModule, 'launchProvidenceWithExtendDocs')
.returns(Promise.resolve());
});
after(() => {
extendDocsStub.restore();
});
afterEach(() => {
extendDocsStub.resetHistory();
});
it('allows configuration', async () => {
await runCli(
[
'extend-docs',
'-t /xyz',
'-r /xyz/x',
'--prefix-from pfrom --prefix-to pto',
'--output-folder /outp',
'--extensions bla',
'--allowlist al --allowlist-reference alr',
].join(' '),
rootDir,
);
expect(extendDocsStub.called).to.be.true;
expect(extendDocsStub.args[0][0]).to.eql({
referenceProjectPaths: ['/xyz/x'],
prefixCfg: {
from: 'pfrom',
to: 'pto',
},
outputFolder: '/outp',
extensions: ['.bla'],
allowlist: ['al'],
allowlistReference: ['alr'],
cwd: '/mocked/path/example-project',
skipCheckMatchCompatibility: true,
});
});
});
});
});

View file

@ -1,807 +0,0 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable import/no-extraneous-dependencies */
import sinon from 'sinon';
import pathLib from 'path';
import { fileURLToPath } from 'url';
import { expect } from 'chai';
import commander from 'commander';
import {
mockProject,
restoreMockedProjects,
mockTargetAndReferenceProject,
} from '../../test-helpers/mock-project-helpers.js';
import {
mockWriteToJson,
restoreWriteToJson,
} from '../../test-helpers/mock-report-service-helpers.js';
import {
suppressNonCriticalLogs,
restoreSuppressNonCriticalLogs,
} from '../../test-helpers/mock-log-service-helpers.js';
import { InputDataService } from '../../src/program/core/InputDataService.js';
import { QueryService } from '../../src/program/core/QueryService.js';
import providenceModule from '../../src/program/providence.js';
import cliHelpersModule from '../../src/cli/cli-helpers.js';
import { cli } from '../../src/cli/cli.mjs';
import promptAnalyzerModule from '../../src/cli/prompt-analyzer-menu.js';
import { toPosixPath } from '../../src/program/utils/to-posix-path.js';
import { memoizeConfig } from '../../src/program/utils/memoize.js';
import extendDocsModule, {
getExtendDocsResults,
} from '../../src/cli/launch-providence-with-extend-docs.js';
import { dashboardServer } from '../../dashboard/server.mjs';
/**
* @typedef {import('../../src/program/types/core').QueryResult} QueryResult
*/
const __dirname = pathLib.dirname(fileURLToPath(import.meta.url));
const { pathsArrayFromCs, pathsArrayFromCollectionName, appendProjectDependencyPaths } =
cliHelpersModule;
/** @type {QueryResult[]} */
const queryResults = [];
const externalCfgMock = {
searchTargetCollections: {
'lion-collection': [
'./providence-input-data/search-targets/example-project-a',
'./providence-input-data/search-targets/example-project-b',
// ...etc
],
},
referenceCollections: {
'lion-based-ui-collection': [
'./providence-input-data/references/lion-based-ui',
'./providence-input-data/references/lion-based-ui-labs',
],
},
};
/**
* @param {string} args
* @param {string} cwd
*/
async function runCli(args, cwd) {
const argv = [
...process.argv.slice(0, 2),
...args.split(' ').map(a => a.replace(/^("|')?(.*)("|')?$/, '$2')),
];
await cli({ argv, cwd });
}
describe('Providence CLI', () => {
const rootDir = '/mocked/path/example-project';
/** @type {sinon.SinonStub} */
let providenceStub;
/** @type {sinon.SinonStub} */
let promptCfgStub;
/** @type {sinon.SinonStub} */
let iExtConfStub;
/** @type {sinon.SinonStub} */
let promptStub;
/** @type {sinon.SinonStub} */
let qConfStub;
const memoizeCacheDisabledInitial = memoizeConfig.isCacheDisabled;
memoizeConfig.isCacheDisabled = true;
before(() => {
// Prevent MaxListenersExceededWarning
commander.setMaxListeners(100);
mockWriteToJson(queryResults);
suppressNonCriticalLogs();
mockProject(
{
'./src/OriginalComp.js': `export class OriginalComp {}`,
'./src/inbetween.js': `export { OriginalComp as InBetweenComp } from './OriginalComp.js'`,
'./index.js': `export { InBetweenComp as MyComp } from './src/inbetween.js'`,
'./node_modules/dependency-a/index.js': '',
'./bower_components/dependency-b/index.js': '',
},
{
projectName: 'example-project',
projectPath: '/mocked/path/example-project',
},
);
/** @type {sinon.SinonStub} */
providenceStub = sinon.stub(providenceModule, 'providence').returns(Promise.resolve());
/** @type {sinon.SinonStub} */
promptCfgStub = sinon
.stub(promptAnalyzerModule, 'promptAnalyzerConfigMenu')
.returns(Promise.resolve({ analyzerConfig: { con: 'fig' } }));
/** @type {sinon.SinonStub} */
iExtConfStub = sinon.stub(InputDataService, 'getExternalConfig').returns(externalCfgMock);
/** @type {sinon.SinonStub} */
promptStub = sinon
.stub(promptAnalyzerModule, 'promptAnalyzerMenu')
.returns(Promise.resolve({ analyzerName: 'match-analyzer-mock' }));
/** @type {sinon.SinonStub} */
qConfStub = sinon.stub(QueryService, 'getQueryConfigFromAnalyzer').returns({
analyzer: {
name: 'match-analyzer-mock',
requiresReference: true,
},
});
});
after(() => {
commander.setMaxListeners(10);
restoreSuppressNonCriticalLogs();
restoreMockedProjects();
restoreWriteToJson();
providenceStub.restore();
promptCfgStub.restore();
iExtConfStub.restore();
promptStub.restore();
qConfStub.restore();
memoizeConfig.isCacheDisabled = memoizeCacheDisabledInitial;
});
beforeEach(() => {
memoizeConfig.isCacheDisabled = true;
});
afterEach(() => {
providenceStub.resetHistory();
promptCfgStub.resetHistory();
iExtConfStub.resetHistory();
promptStub.resetHistory();
qConfStub.resetHistory();
});
const analyzeCmd = 'analyze match-analyzer-mock';
it('calls providence', async () => {
await runCli(`${analyzeCmd} -t /mocked/path/example-project`, rootDir);
expect(providenceStub.called).to.be.true;
});
it('creates a QueryConfig', async () => {
await runCli(`${analyzeCmd} -t /mocked/path/example-project`, rootDir);
expect(qConfStub.called).to.be.true;
expect(qConfStub.args[0][0]).to.equal('match-analyzer-mock');
});
describe('Global options', () => {
/** @type {sinon.SinonStub} */
let pathsArrayFromCollectionStub;
/** @type {sinon.SinonStub} */
let pathsArrayFromCsStub;
/** @type {sinon.SinonStub} */
let appendProjectDependencyPathsStub;
before(() => {
pathsArrayFromCsStub = sinon
.stub(cliHelpersModule, 'pathsArrayFromCs')
.returns(['/mocked/path/example-project']);
pathsArrayFromCollectionStub = sinon
.stub(cliHelpersModule, 'pathsArrayFromCollectionName')
.returns(['/mocked/path/example-project']);
appendProjectDependencyPathsStub = sinon
.stub(cliHelpersModule, 'appendProjectDependencyPaths')
.returns(
Promise.resolve([
'/mocked/path/example-project',
'/mocked/path/example-project/node_modules/mock-dep-a',
'/mocked/path/example-project/bower_components/mock-dep-b',
]),
);
});
after(() => {
pathsArrayFromCsStub.restore();
pathsArrayFromCollectionStub.restore();
appendProjectDependencyPathsStub.restore();
});
afterEach(() => {
pathsArrayFromCsStub.resetHistory();
pathsArrayFromCollectionStub.resetHistory();
appendProjectDependencyPathsStub.resetHistory();
});
it('"-e --extensions"', async () => {
await runCli(`${analyzeCmd} -e bla,blu`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfig.extensions).to.eql(['.bla', '.blu']);
providenceStub.resetHistory();
await runCli(`${analyzeCmd} --extensions bla,blu`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfig.extensions).to.eql(['.bla', '.blu']);
});
it('"-t --search-target-paths"', async () => {
await runCli(`${analyzeCmd} -t /mocked/path/example-project`, rootDir);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].targetProjectPaths).to.eql(['/mocked/path/example-project']);
pathsArrayFromCsStub.resetHistory();
providenceStub.resetHistory();
await runCli(`${analyzeCmd} --search-target-paths /mocked/path/example-project`, rootDir);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].targetProjectPaths).to.eql(['/mocked/path/example-project']);
});
it('"-r --reference-paths"', async () => {
await runCli(`${analyzeCmd} -r /mocked/path/example-project`, rootDir);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].referenceProjectPaths).to.eql([
'/mocked/path/example-project',
]);
pathsArrayFromCsStub.resetHistory();
providenceStub.resetHistory();
await runCli(`${analyzeCmd} --reference-paths /mocked/path/example-project`, rootDir);
expect(pathsArrayFromCsStub.args[0][0]).to.equal('/mocked/path/example-project');
expect(providenceStub.args[0][1].referenceProjectPaths).to.eql([
'/mocked/path/example-project',
]);
});
it('"--search-target-collection"', async () => {
await runCli(`${analyzeCmd} --search-target-collection lion-collection`, rootDir);
expect(pathsArrayFromCollectionStub.args[0][0]).to.equal('lion-collection');
expect(providenceStub.args[0][1].targetProjectPaths).to.eql(['/mocked/path/example-project']);
});
it('"--reference-collection"', async () => {
await runCli(`${analyzeCmd} --reference-collection lion-based-ui-collection`, rootDir);
expect(pathsArrayFromCollectionStub.args[0][0]).to.equal('lion-based-ui-collection');
expect(providenceStub.args[0][1].referenceProjectPaths).to.eql([
'/mocked/path/example-project',
]);
});
it('"-a --allowlist"', async () => {
await runCli(`${analyzeCmd} -a mocked/**/*,rocked/*`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfig.allowlist).to.eql([
'mocked/**/*',
'rocked/*',
]);
providenceStub.resetHistory();
await runCli(`${analyzeCmd} --allowlist mocked/**/*,rocked/*`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfig.allowlist).to.eql([
'mocked/**/*',
'rocked/*',
]);
});
it('"--allowlist-reference"', async () => {
await runCli(`${analyzeCmd} --allowlist-reference mocked/**/*,rocked/*`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfigReference.allowlist).to.eql([
'mocked/**/*',
'rocked/*',
]);
});
it('--allowlist-mode', async () => {
await runCli(`${analyzeCmd} --allowlist-mode git`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfig.allowlistMode).to.equal('git');
});
it('--allowlist-mode-reference', async () => {
await runCli(`${analyzeCmd} --allowlist-mode-reference npm`, rootDir);
expect(providenceStub.args[0][1].gatherFilesConfigReference.allowlistMode).to.equal('npm');
});
it('"-D --debug"', async () => {
await runCli(`${analyzeCmd} -D`, rootDir);
expect(providenceStub.args[0][1].debugEnabled).to.equal(true);
providenceStub.resetHistory();
await runCli(`${analyzeCmd} --debug`, rootDir);
expect(providenceStub.args[0][1].debugEnabled).to.equal(true);
});
it('--write-log-file"', async () => {
await runCli(`${analyzeCmd} --write-log-file`, rootDir);
expect(providenceStub.args[0][1].writeLogFile).to.equal(true);
});
it('--target-dependencies"', async () => {
await runCli(`${analyzeCmd}`, rootDir);
expect(appendProjectDependencyPathsStub.called).to.be.false;
appendProjectDependencyPathsStub.resetHistory();
providenceStub.resetHistory();
await runCli(`${analyzeCmd} --target-dependencies`, rootDir);
expect(appendProjectDependencyPathsStub.called).to.be.true;
expect(providenceStub.args[0][1].targetProjectPaths).to.eql([
'/mocked/path/example-project',
'/mocked/path/example-project/node_modules/mock-dep-a',
'/mocked/path/example-project/bower_components/mock-dep-b',
]);
});
it('--target-dependencies /^with-regex/"', async () => {
await runCli(`${analyzeCmd} --target-dependencies /^mock-/`, rootDir);
expect(appendProjectDependencyPathsStub.args[0][1]).to.equal('/^mock-/');
});
it('"--skip-check-match-compatibility"', async () => {
await runCli(`${analyzeCmd} --skip-check-match-compatibility`, rootDir);
expect(providenceStub.args[0][1].skipCheckMatchCompatibility).to.equal(true);
});
});
describe('Commands', () => {
describe('Analyze', () => {
it('calls providence', async () => {
await runCli(`${analyzeCmd}`, rootDir);
expect(providenceStub.called).to.be.true;
});
describe('Options', () => {
it('"-o --prompt-optional-config"', async () => {
await runCli(`analyze -o`, rootDir);
expect(promptStub.called).to.be.true;
promptStub.resetHistory();
await runCli(`analyze --prompt-optional-config`, rootDir);
expect(promptStub.called).to.be.true;
});
it('"-c --config"', async () => {
await runCli(`analyze match-analyzer-mock -c {"a":"2"}`, rootDir);
expect(qConfStub.args[0][0]).to.equal('match-analyzer-mock');
expect(qConfStub.args[0][1]).to.eql({ a: '2', metaConfig: {} });
qConfStub.resetHistory();
await runCli(`analyze match-analyzer-mock --config {"a":"2"}`, rootDir);
expect(qConfStub.args[0][0]).to.equal('match-analyzer-mock');
expect(qConfStub.args[0][1]).to.eql({ a: '2', metaConfig: {} });
});
it('calls "promptAnalyzerConfigMenu" without config given', async () => {
await runCli(`analyze match-analyzer-mock`, rootDir);
expect(promptCfgStub.called).to.be.true;
});
});
});
describe.skip('Query', () => {});
describe.skip('Search', () => {});
describe('Manage', () => {});
describe('Dashboard', () => {
/** @type {sinon.SinonStub} */
const startStub = sinon.stub(dashboardServer, 'start');
it('spawns a dashboard', async () => {
runCli(`dashboard`, rootDir);
expect(startStub.called).to.be.true;
});
});
describe('Extend docs', () => {
/** @type {sinon.SinonStub} */
let extendDocsStub;
before(() => {
extendDocsStub = sinon
.stub(extendDocsModule, 'launchProvidenceWithExtendDocs')
.returns(Promise.resolve());
});
after(() => {
extendDocsStub.restore();
});
afterEach(() => {
extendDocsStub.resetHistory();
});
it('allows configuration', async () => {
await runCli(
[
'extend-docs',
'-t /xyz',
'-r /xyz/x',
'--prefix-from pfrom --prefix-to pto',
'--output-folder /outp',
'--extensions bla',
'--allowlist al --allowlist-reference alr',
].join(' '),
rootDir,
);
expect(extendDocsStub.called).to.be.true;
expect(extendDocsStub.args[0][0]).to.eql({
referenceProjectPaths: ['/xyz/x'],
prefixCfg: {
from: 'pfrom',
to: 'pto',
},
outputFolder: '/outp',
extensions: ['.bla'],
allowlist: ['al'],
allowlistReference: ['alr'],
cwd: '/mocked/path/example-project',
skipCheckMatchCompatibility: true,
});
});
});
});
});
describe('CLI helpers', () => {
const rootDir = toPosixPath(pathLib.resolve(__dirname, '../../'));
describe('pathsArrayFromCs', () => {
it('allows absolute paths', async () => {
expect(pathsArrayFromCs('/mocked/path/example-project', rootDir)).to.eql([
'/mocked/path/example-project',
]);
});
it('allows relative paths', async () => {
expect(
pathsArrayFromCs('./test-helpers/project-mocks/importing-target-project', rootDir),
).to.eql([`${rootDir}/test-helpers/project-mocks/importing-target-project`]);
expect(
pathsArrayFromCs('test-helpers/project-mocks/importing-target-project', rootDir),
).to.eql([`${rootDir}/test-helpers/project-mocks/importing-target-project`]);
});
it('allows globs', async () => {
expect(pathsArrayFromCs('test-helpers/project-mocks*', rootDir)).to.eql([
`${rootDir}/test-helpers/project-mocks`,
`${rootDir}/test-helpers/project-mocks-analyzer-outputs`,
]);
});
it('allows multiple comma separated paths', async () => {
const paths =
'test-helpers/project-mocks*, ./test-helpers/project-mocks/importing-target-project,/mocked/path/example-project';
expect(pathsArrayFromCs(paths, rootDir)).to.eql([
`${rootDir}/test-helpers/project-mocks`,
`${rootDir}/test-helpers/project-mocks-analyzer-outputs`,
`${rootDir}/test-helpers/project-mocks/importing-target-project`,
'/mocked/path/example-project',
]);
});
});
describe('pathsArrayFromCollectionName', () => {
it('gets collections from external target config', async () => {
expect(
pathsArrayFromCollectionName('lion-collection', 'search-target', externalCfgMock, rootDir),
).to.eql(
externalCfgMock.searchTargetCollections['lion-collection'].map(p =>
toPosixPath(pathLib.join(rootDir, p)),
),
);
});
it('gets collections from external reference config', async () => {
expect(
pathsArrayFromCollectionName(
'lion-based-ui-collection',
'reference',
externalCfgMock,
rootDir,
),
).to.eql(
externalCfgMock.referenceCollections['lion-based-ui-collection'].map(p =>
toPosixPath(pathLib.join(rootDir, p)),
),
);
});
});
describe('appendProjectDependencyPaths', () => {
before(() => {
mockWriteToJson(queryResults);
suppressNonCriticalLogs();
mockProject(
{
'./src/OriginalComp.js': `export class OriginalComp {}`,
'./src/inbetween.js': `export { OriginalComp as InBetweenComp } from './OriginalComp.js'`,
'./index.js': `export { InBetweenComp as MyComp } from './src/inbetween.js'`,
'./node_modules/dependency-a/index.js': '',
'./bower_components/dependency-b/index.js': '',
'./node_modules/my-dependency/index.js': '',
},
{
projectName: 'example-project',
projectPath: '/mocked/path/example-project',
},
);
});
it('adds bower and node dependencies', async () => {
const result = await appendProjectDependencyPaths(['/mocked/path/example-project']);
expect(result).to.eql([
'/mocked/path/example-project/node_modules/dependency-a',
'/mocked/path/example-project/node_modules/my-dependency',
'/mocked/path/example-project/bower_components/dependency-b',
'/mocked/path/example-project',
]);
});
it('allows a regex filter', async () => {
const result = await appendProjectDependencyPaths(
['/mocked/path/example-project'],
'/^dependency-/',
);
expect(result).to.eql([
'/mocked/path/example-project/node_modules/dependency-a',
// in windows, it should not add '/mocked/path/example-project/node_modules/my-dependency',
'/mocked/path/example-project/bower_components/dependency-b',
'/mocked/path/example-project',
]);
const result2 = await appendProjectDependencyPaths(['/mocked/path/example-project'], '/b$/');
expect(result2).to.eql([
'/mocked/path/example-project/bower_components/dependency-b',
'/mocked/path/example-project',
]);
});
it('allows to filter out only npm or bower deps', async () => {
const result = await appendProjectDependencyPaths(['/mocked/path/example-project'], null, [
'npm',
]);
expect(result).to.eql([
'/mocked/path/example-project/node_modules/dependency-a',
'/mocked/path/example-project/node_modules/my-dependency',
'/mocked/path/example-project',
]);
const result2 = await appendProjectDependencyPaths(['/mocked/path/example-project'], null, [
'bower',
]);
expect(result2).to.eql([
'/mocked/path/example-project/bower_components/dependency-b',
'/mocked/path/example-project',
]);
});
});
describe('Extend docs', () => {
afterEach(() => {
restoreMockedProjects();
});
it('rewrites monorepo package paths when analysis is run from monorepo root', async () => {
// This fails after InputDataService.addAstToProjectsData is memoized
// (it does pass when run in isolation however, as a quick fix we disable memoization cache here...)
memoizeConfig.isCacheDisabled = true;
const theirProjectFiles = {
'./package.json': JSON.stringify({
name: 'their-components',
version: '1.0.0',
}),
'./src/TheirButton.js': `export class TheirButton extends HTMLElement {}`,
'./src/TheirTooltip.js': `export class TheirTooltip extends HTMLElement {}`,
'./their-button.js': `
import { TheirButton } from './src/TheirButton.js';
customElements.define('their-button', TheirButton);
`,
'./demo.js': `
import { TheirTooltip } from './src/TheirTooltip.js';
import './their-button.js';
`,
};
const myProjectFiles = {
'./package.json': JSON.stringify({
name: '@my/root',
workspaces: ['packages/*', 'another-folder/my-tooltip'],
dependencies: {
'their-components': '1.0.0',
},
}),
// Package 1: @my/button
'./packages/button/package.json': JSON.stringify({
name: '@my/button',
}),
'./packages/button/src/MyButton.js': `
import { TheirButton } from 'their-components/src/TheirButton.js';
export class MyButton extends TheirButton {}
`,
'./packages/button/src/my-button.js': `
import { MyButton } from './MyButton.js';
customElements.define('my-button', MyButton);
`,
// Package 2: @my/tooltip
'./packages/tooltip/package.json': JSON.stringify({
name: '@my/tooltip',
}),
'./packages/tooltip/src/MyTooltip.js': `
import { TheirTooltip } from 'their-components/src/TheirTooltip.js';
export class MyTooltip extends TheirTooltip {}
`,
};
const theirProject = {
path: '/my-components/node_modules/their-components',
name: 'their-components',
files: Object.entries(theirProjectFiles).map(([file, code]) => ({ file, code })),
};
const myProject = {
path: '/my-components',
name: 'my-components',
files: Object.entries(myProjectFiles).map(([file, code]) => ({ file, code })),
};
mockTargetAndReferenceProject(theirProject, myProject);
const result = await getExtendDocsResults({
referenceProjectPaths: [theirProject.path],
prefixCfg: { from: 'their', to: 'my' },
extensions: ['.js'],
cwd: '/my-components',
});
expect(result).to.eql([
{
name: 'TheirButton',
variable: {
from: 'TheirButton',
to: 'MyButton',
paths: [
{
from: './src/TheirButton.js',
to: '@my/button/src/MyButton.js', // rewritten from './packages/button/src/MyButton.js',
},
{
from: 'their-components/src/TheirButton.js',
to: '@my/button/src/MyButton.js', // rewritten from './packages/button/src/MyButton.js',
},
],
},
tag: {
from: 'their-button',
to: 'my-button',
paths: [
{
from: './their-button.js',
to: '@my/button/src/my-button.js', // rewritten from './packages/button/src/MyButton.js',
},
{
from: 'their-components/their-button.js',
to: '@my/button/src/my-button.js', // rewritten from './packages/button/src/MyButton.js',
},
],
},
},
{
name: 'TheirTooltip',
variable: {
from: 'TheirTooltip',
to: 'MyTooltip',
paths: [
{
from: './src/TheirTooltip.js',
to: '@my/tooltip/src/MyTooltip.js', // './packages/tooltip/src/MyTooltip.js',
},
{
from: 'their-components/src/TheirTooltip.js',
to: '@my/tooltip/src/MyTooltip.js', // './packages/tooltip/src/MyTooltip.js',
},
],
},
},
]);
});
it('does not check for match compatibility (target and reference) in monorepo targets', async () => {
// ===== REFERENCE AND TARGET PROJECTS =====
const theirProjectFiles = {
'./package.json': JSON.stringify({
name: 'their-components',
version: '1.0.0',
}),
'./src/TheirButton.js': `export class TheirButton extends HTMLElement {}`,
};
// This will be detected as being a monorepo
const monoProjectFiles = {
'./package.json': JSON.stringify({
name: '@mono/root',
workspaces: ['packages/*'],
dependencies: {
'their-components': '1.0.0',
},
}),
// Package: @mono/button
'./packages/button/package.json': JSON.stringify({
name: '@mono/button',
}),
};
// This will be detected as NOT being a monorepo
const nonMonoProjectFiles = {
'./package.json': JSON.stringify({
name: 'non-mono',
dependencies: {
'their-components': '1.0.0',
},
}),
};
const theirProject = {
path: '/their-components',
name: 'their-components',
files: Object.entries(theirProjectFiles).map(([file, code]) => ({ file, code })),
};
const monoProject = {
path: '/mono-components',
name: 'mono-components',
files: Object.entries(monoProjectFiles).map(([file, code]) => ({ file, code })),
};
const nonMonoProject = {
path: '/non-mono-components',
name: 'non-mono-components',
files: Object.entries(nonMonoProjectFiles).map(([file, code]) => ({ file, code })),
};
// ===== TESTS =====
const providenceStub = sinon.stub(providenceModule, 'providence').returns(
new Promise(resolve => {
resolve([]);
}),
);
// ===== mono =====
mockTargetAndReferenceProject(theirProject, monoProject);
await getExtendDocsResults({
referenceProjectPaths: ['/their-components'],
prefixCfg: { from: 'their', to: 'my' },
extensions: ['.js'],
cwd: '/mono-components',
});
expect(providenceStub.args[0][1].skipCheckMatchCompatibility).to.equal(true);
providenceStub.resetHistory();
restoreMockedProjects();
// ===== non mono =====
mockTargetAndReferenceProject(theirProject, nonMonoProject);
await getExtendDocsResults({
referenceProjectPaths: ['/their-components'],
prefixCfg: { from: 'their', to: 'my' },
extensions: ['.js'],
cwd: '/non-mono-components',
});
expect(providenceStub.args[0][1].skipCheckMatchCompatibility).to.equal(false);
providenceStub.restore();
});
});
});

View file

@ -6,9 +6,9 @@ import { fileURLToPath } from 'url';
import { expect } from 'chai'; import { expect } from 'chai';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import { createTestServer } from '@web/dev-server-core/test-helpers'; import { createTestServer } from '@web/dev-server-core/test-helpers';
import { createDashboardServerConfig } from '../../dashboard/server.mjs'; import { createDashboardServerConfig } from '../../dashboard/server.js';
import { ReportService } from '../../src/program/core/ReportService.js'; import { ReportService } from '../../src/program/core/ReportService.js';
import { providenceConfUtil } from '../../src/program/utils/providence-conf-util.mjs'; import { providenceConfUtil } from '../../src/program/utils/providence-conf-util.js';
/** /**
* @typedef {import('@web/dev-server-core').DevServer} DevServer * @typedef {import('@web/dev-server-core').DevServer} DevServer

View file

@ -3,18 +3,31 @@ import pathLib, { dirname } from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import fs from 'fs'; import fs from 'fs';
import { expect } from 'chai'; import { expect } from 'chai';
import { it } from 'mocha';
import { providence } from '../../../../src/program/providence.js'; import { providence } from '../../../../src/program/providence.js';
import { QueryService } from '../../../../src/program/core/QueryService.js'; import { QueryService } from '../../../../src/program/core/QueryService.js';
import { ReportService } from '../../../../src/program/core/ReportService.js'; import { ReportService } from '../../../../src/program/core/ReportService.js';
import { memoizeConfig } from '../../../../src/program/utils/memoize.js'; import { memoizeConfig } from '../../../../src/program/utils/memoize.js';
import { setupAnalyzerTest } from '../../../../test-helpers/setup-analyzer-test.js';
import { import {
mockWriteToJson, FindExportsAnalyzer,
restoreWriteToJson, FindImportsAnalyzer,
} from '../../../../test-helpers/mock-report-service-helpers.js'; MatchImportsAnalyzer,
} from '../../../../src/program/analyzers/index.js';
import MatchSubclassesAnalyzer from '../../../../src/program/analyzers/match-subclasses.js';
import MatchPathsAnalyzer from '../../../../src/program/analyzers/match-paths.js';
import FindCustomelementsAnalyzer from '../../../../src/program/analyzers/find-customelements.js';
import FindClassesAnalyzer from '../../../../src/program/analyzers/find-classes.js';
/**
* @typedef {import('../../../../types/index.js').ProvidenceConfig} ProvidenceConfig
* @typedef {import('../../../../types/index.js').QueryResult} QueryResult
*/
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
setupAnalyzerTest();
describe('Analyzers file-system integration', () => { describe('Analyzers file-system integration', () => {
/** /**
* Flag to enable mode that generates e2e mocks. * Flag to enable mode that generates e2e mocks.
@ -23,7 +36,6 @@ describe('Analyzers file-system integration', () => {
*/ */
const generateE2eMode = process.argv.includes('--generate-e2e-mode'); const generateE2eMode = process.argv.includes('--generate-e2e-mode');
const queryResults = [];
const targetPath = pathLib.resolve( const targetPath = pathLib.resolve(
__dirname, __dirname,
'../../../../test-helpers/project-mocks/importing-target-project', '../../../../test-helpers/project-mocks/importing-target-project',
@ -50,73 +62,69 @@ describe('Analyzers file-system integration', () => {
__dirname, __dirname,
'../../../../test-helpers/project-mocks-analyzer-outputs', '../../../../test-helpers/project-mocks-analyzer-outputs',
); );
// @ts-expect-error
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
ReportService._getResultFileNameAndPath = function (name) { ReportService._getResultFileNameAndPath = function (name) {
return pathLib.join(this.outputPath, `${name}.json`); return pathLib.join(this.outputPath, `${name}.json`);
}; };
} else { } else {
ReportService.outputPath = __dirname; // prevents cache to fail the test ReportService.outputPath = __dirname; // prevents cache to fail the test
beforeEach(() => {
mockWriteToJson(queryResults);
});
afterEach(() => {
restoreWriteToJson(queryResults);
});
} }
const analyzers = [ const analyzers = [
{ {
analyzerName: 'find-customelements',
providenceConfig: { providenceConfig: {
targetProjectPaths: [targetPath], targetProjectPaths: [targetPath],
}, },
ctor: FindCustomelementsAnalyzer,
}, },
{ {
analyzerName: 'find-imports',
providenceConfig: { providenceConfig: {
targetProjectPaths: [targetPath], targetProjectPaths: [targetPath],
}, },
ctor: FindImportsAnalyzer,
}, },
{ {
analyzerName: 'find-exports',
providenceConfig: { providenceConfig: {
targetProjectPaths: [referencePath], targetProjectPaths: [referencePath],
}, },
ctor: FindExportsAnalyzer,
}, },
{ {
analyzerName: 'find-classes',
providenceConfig: { providenceConfig: {
targetProjectPaths: [targetPath], targetProjectPaths: [targetPath],
}, },
ctor: FindClassesAnalyzer,
}, },
{ {
analyzerName: 'match-imports',
providenceConfig: { providenceConfig: {
targetProjectPaths: [targetPath], targetProjectPaths: [targetPath],
referenceProjectPaths: [referencePath], referenceProjectPaths: [referencePath],
}, },
ctor: MatchImportsAnalyzer,
}, },
{ {
analyzerName: 'match-subclasses',
providenceConfig: { providenceConfig: {
targetProjectPaths: [targetPath], targetProjectPaths: [targetPath],
referenceProjectPaths: [referencePath], referenceProjectPaths: [referencePath],
}, },
ctor: MatchSubclassesAnalyzer,
}, },
{ {
analyzerName: 'match-paths',
providenceConfig: { providenceConfig: {
targetProjectPaths: [targetPath], targetProjectPaths: [targetPath],
referenceProjectPaths: [referencePath], referenceProjectPaths: [referencePath],
}, },
ctor: MatchPathsAnalyzer,
}, },
]; ];
for (const { analyzerName, providenceConfig } of analyzers) { for (const { ctor, providenceConfig } of analyzers) {
it(`"${analyzerName}" analyzer`, async () => { it(`"${ctor.analyzerName}" analyzer`, async () => {
const findExportsQueryConfig = QueryService.getQueryConfigFromAnalyzer(analyzerName); const findExportsQueryConfig = await QueryService.getQueryConfigFromAnalyzer(ctor);
await providence(findExportsQueryConfig, providenceConfig); const queryResults = await providence(
findExportsQueryConfig,
/** @type {ProvidenceConfig} */ (providenceConfig),
);
if (generateE2eMode) { if (generateE2eMode) {
console.info( console.info(
'Successfully created mocks. Do not forget to rerun tests now without "--generate-e2e-mode"', 'Successfully created mocks. Do not forget to rerun tests now without "--generate-e2e-mode"',
@ -127,7 +135,7 @@ describe('Analyzers file-system integration', () => {
fs.readFileSync( fs.readFileSync(
pathLib.resolve( pathLib.resolve(
__dirname, __dirname,
`../../../../test-helpers/project-mocks-analyzer-outputs/${analyzerName}.json`, `../../../../test-helpers/project-mocks-analyzer-outputs/${ctor.analyzerName}.json`,
), ),
'utf8', 'utf8',
), ),

View file

@ -1,22 +1,30 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { providence } = require('../../../src/program/providence.js'); import { it } from 'mocha';
const { QueryService } = require('../../../src/program/core/QueryService.js'); import { providence } from '../../../src/program/providence.js';
const { setupAnalyzerTest } = require('../../../test-helpers/setup-analyzer-test.js'); import { QueryService } from '../../../src/program/core/QueryService.js';
const { mockProject, getEntry } = require('../../../test-helpers/mock-project-helpers.js'); import { mockProject, getEntry } from '../../../test-helpers/mock-project-helpers.js';
import { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js';
import FindClassesAnalyzer from '../../../src/program/analyzers/find-classes.js';
const findClassesQueryConfig = QueryService.getQueryConfigFromAnalyzer('find-classes'); /**
* @typedef {import('../../../types/index.js').ProvidenceConfig} ProvidenceConfig
*/
describe('Analyzer "find-classes"', () => { setupAnalyzerTest();
const queryResults = setupAnalyzerTest();
describe('Analyzer "find-classes"', async () => {
const findClassesQueryConfig = await QueryService.getQueryConfigFromAnalyzer(FindClassesAnalyzer);
// const queryResults = setupAnalyzerTest();
/** @type {Partial<ProvidenceConfig>} */
const _providenceCfg = { const _providenceCfg = {
targetProjectPaths: ['/fictional/project'], // defined in mockProject targetProjectPaths: ['/fictional/project'], // defined in mockProject
}; };
it(`finds class definitions`, async () => { it(`finds class definitions`, async () => {
mockProject([`class EmptyClass {}`]); mockProject([`class EmptyClass {}`]);
await providence(findClassesQueryConfig, _providenceCfg); const queryResults = await providence(findClassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result).to.eql([ expect(firstEntry.result).to.eql([
{ {
name: 'EmptyClass', name: 'EmptyClass',
@ -31,9 +39,8 @@ describe('Analyzer "find-classes"', () => {
it(`finds mixin definitions`, async () => { it(`finds mixin definitions`, async () => {
mockProject([`const m = superclass => class MyMixin extends superclass {}`]); mockProject([`const m = superclass => class MyMixin extends superclass {}`]);
await providence(findClassesQueryConfig, _providenceCfg); const queryResults = await providence(findClassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result).to.eql([ expect(firstEntry.result).to.eql([
{ {
name: 'MyMixin', name: 'MyMixin',
@ -63,9 +70,8 @@ describe('Analyzer "find-classes"', () => {
`, `,
'./internal.js': '', './internal.js': '',
}); });
await providence(findClassesQueryConfig, _providenceCfg); const queryResults = await providence(findClassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[1].superClasses).to.eql([ expect(firstEntry.result[1].superClasses).to.eql([
{ {
isMixin: true, isMixin: true,
@ -85,9 +91,8 @@ describe('Analyzer "find-classes"', () => {
` const m = superclass => class MyMixin extends superclass {} ` const m = superclass => class MyMixin extends superclass {}
class EmptyClass extends Mixin(OtherClass) {}`, class EmptyClass extends Mixin(OtherClass) {}`,
]); ]);
await providence(findClassesQueryConfig, _providenceCfg); const queryResults = await providence(findClassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result.length).to.equal(2); expect(firstEntry.result.length).to.equal(2);
}); });
@ -102,9 +107,8 @@ describe('Analyzer "find-classes"', () => {
$$privateMethod() {} $$privateMethod() {}
}`, }`,
]); ]);
await providence(findClassesQueryConfig, _providenceCfg); const queryResults = await providence(findClassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].members.methods).to.eql([ expect(firstEntry.result[0].members.methods).to.eql([
{ {
accessType: 'public', accessType: 'public',
@ -139,9 +143,8 @@ describe('Analyzer "find-classes"', () => {
static set _staticGetterSetter(v) {} static set _staticGetterSetter(v) {}
}`, }`,
]); ]);
await providence(findClassesQueryConfig, _providenceCfg); const queryResults = await providence(findClassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].members.props).to.eql([ expect(firstEntry.result[0].members.props).to.eql([
{ {
accessType: 'public', accessType: 'public',
@ -168,9 +171,8 @@ describe('Analyzer "find-classes"', () => {
disconnectedCallback() {} disconnectedCallback() {}
}`, }`,
]); ]);
await providence(findClassesQueryConfig, _providenceCfg); const queryResults = await providence(findClassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].members.methods.length).to.equal(0); expect(firstEntry.result[0].members.methods.length).to.equal(0);
expect(firstEntry.result[0].members.props.length).to.equal(0); expect(firstEntry.result[0].members.props.length).to.equal(0);
}); });
@ -190,9 +192,8 @@ describe('Analyzer "find-classes"', () => {
shouldUpdate() {} shouldUpdate() {}
}`, }`,
]); ]);
await providence(findClassesQueryConfig, _providenceCfg); const queryResults = await providence(findClassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].members.methods.length).to.equal(0); expect(firstEntry.result[0].members.methods.length).to.equal(0);
expect(firstEntry.result[0].members.props.length).to.equal(0); expect(firstEntry.result[0].members.props.length).to.equal(0);
}); });
@ -205,9 +206,8 @@ describe('Analyzer "find-classes"', () => {
onLocaleUpdated() {} onLocaleUpdated() {}
}`, }`,
]); ]);
await providence(findClassesQueryConfig, _providenceCfg); const queryResults = await providence(findClassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].members.methods.length).to.equal(0); expect(firstEntry.result[0].members.methods.length).to.equal(0);
expect(firstEntry.result[0].members.props.length).to.equal(0); expect(firstEntry.result[0].members.props.length).to.equal(0);
}); });

View file

@ -1,22 +1,29 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { providence } = require('../../../src/program/providence.js'); import { it } from 'mocha';
const { QueryService } = require('../../../src/program/core/QueryService.js'); import { providence } from '../../../src/program/providence.js';
const { setupAnalyzerTest } = require('../../../test-helpers/setup-analyzer-test.js'); import { QueryService } from '../../../src/program/core/QueryService.js';
import { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js';
import { mockProject, getEntry } from '../../../test-helpers/mock-project-helpers.js';
import FindCustomelementsAnalyzer from '../../../src/program/analyzers/find-customelements.js';
const { mockProject, getEntry } = require('../../../test-helpers/mock-project-helpers.js'); /**
* @typedef {import('../../../types/index.js').ProvidenceConfig} ProvidenceConfig
*/
const findCustomelementsQueryConfig = setupAnalyzerTest();
QueryService.getQueryConfigFromAnalyzer('find-customelements');
describe('Analyzer "find-customelements"', async () => {
const findCustomelementsQueryConfig = await QueryService.getQueryConfigFromAnalyzer(
FindCustomelementsAnalyzer,
);
/** @type {Partial<ProvidenceConfig>} */
const _providenceCfg = { const _providenceCfg = {
targetProjectPaths: ['/fictional/project'], // defined in mockProject targetProjectPaths: ['/fictional/project'], // defined in mockProject
}; };
describe('Analyzer "find-customelements"', () => {
const queryResults = setupAnalyzerTest();
it(`stores the tagName of a custom element`, async () => { it(`stores the tagName of a custom element`, async () => {
mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]); mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]);
await providence(findCustomelementsQueryConfig, _providenceCfg); const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].tagName).to.equal('custom-el'); expect(firstEntry.result[0].tagName).to.equal('custom-el');
@ -30,7 +37,7 @@ describe('Analyzer "find-customelements"', () => {
window.customElements.define('custom-el3', class extends HTMLElement {}); window.customElements.define('custom-el3', class extends HTMLElement {});
})();`, })();`,
]); ]);
await providence(findCustomelementsQueryConfig, _providenceCfg); const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
const secondEntry = getEntry(queryResult, 1); const secondEntry = getEntry(queryResult, 1);
@ -48,7 +55,7 @@ describe('Analyzer "find-customelements"', () => {
customElements.define('custom-el', CustomEl); customElements.define('custom-el', CustomEl);
`, `,
}); });
await providence(findCustomelementsQueryConfig, _providenceCfg); const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].rootFile).to.eql({ expect(firstEntry.result[0].rootFile).to.eql({
@ -59,7 +66,7 @@ describe('Analyzer "find-customelements"', () => {
it(`stores "[inline]" constructors`, async () => { it(`stores "[inline]" constructors`, async () => {
mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]); mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]);
await providence(findCustomelementsQueryConfig, _providenceCfg); const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].constructorIdentifier).to.equal('[inline]'); expect(firstEntry.result[0].constructorIdentifier).to.equal('[inline]');
@ -68,7 +75,7 @@ describe('Analyzer "find-customelements"', () => {
it(`stores "[current]" rootFile`, async () => { it(`stores "[current]" rootFile`, async () => {
mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]); mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]);
await providence(findCustomelementsQueryConfig, _providenceCfg); const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].rootFile.file).to.equal('[current]'); expect(firstEntry.result[0].rootFile.file).to.equal('[current]');
@ -82,7 +89,7 @@ describe('Analyzer "find-customelements"', () => {
customElements.define('custom-el', CustomEl); customElements.define('custom-el', CustomEl);
`, `,
}); });
await providence(findCustomelementsQueryConfig, _providenceCfg); const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].constructorIdentifier).to.equal('CustomEl'); expect(firstEntry.result[0].constructorIdentifier).to.equal('CustomEl');
@ -99,7 +106,7 @@ describe('Analyzer "find-customelements"', () => {
customElements.define('tag-3', class extends HTMLElement {}); customElements.define('tag-3', class extends HTMLElement {});
`, `,
]); ]);
await providence(findCustomelementsQueryConfig, _providenceCfg); const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
const secondEntry = getEntry(queryResult, 1); const secondEntry = getEntry(queryResult, 1);

View file

@ -1,18 +1,21 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { providence } = require('../../../src/program/providence.js'); import { it } from 'mocha';
const { QueryService } = require('../../../src/program/core/QueryService.js'); import { providence } from '../../../src/program/providence.js';
const { setupAnalyzerTest } = require('../../../test-helpers/setup-analyzer-test.js'); import { QueryService } from '../../../src/program/core/QueryService.js';
import { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js';
import { mockProject, getEntry, getEntries } from '../../../test-helpers/mock-project-helpers.js';
import FindExportsAnalyzer from '../../../src/program/analyzers/find-exports.js';
const { /**
mockProject, * @typedef {import('../../../types/index.js').ProvidenceConfig} ProvidenceConfig
getEntry, */
getEntries,
} = require('../../../test-helpers/mock-project-helpers.js');
const findExportsQueryConfig = QueryService.getQueryConfigFromAnalyzer('find-exports'); setupAnalyzerTest();
describe('Analyzer "find-exports"', () => { describe('Analyzer "find-exports"', async () => {
const queryResults = setupAnalyzerTest(); const findExportsQueryConfig = await QueryService.getQueryConfigFromAnalyzer(FindExportsAnalyzer);
/** @type {Partial<ProvidenceConfig>} */
const _providenceCfg = { const _providenceCfg = {
targetProjectPaths: ['/fictional/project'], // defined in mockProject targetProjectPaths: ['/fictional/project'], // defined in mockProject
}; };
@ -20,32 +23,28 @@ describe('Analyzer "find-exports"', () => {
describe('Export notations', () => { describe('Export notations', () => {
it(`supports [export const x = 0] (named specifier)`, async () => { it(`supports [export const x = 0] (named specifier)`, async () => {
mockProject([`export const x = 0`]); mockProject([`export const x = 0`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstResult = getEntry(queryResults[0]).result[0];
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstResult.exportSpecifiers).to.eql(['x']);
expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('x'); expect(firstResult.source).to.be.undefined;
expect(firstEntry.result[0].source).to.be.undefined;
}); });
it(`supports [export default class X {}] (default export)`, async () => { it(`supports [export default class X {}] (default export)`, async () => {
mockProject([`export default class X {}`]); mockProject([`export default class X {}`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstResult = getEntry(queryResults[0]).result[0];
const firstEntry = getEntry(queryResult); expect(firstResult.exportSpecifiers).to.eql(['[default]']);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstResult.source).to.be.undefined;
expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]');
expect(firstEntry.result[0].source).to.equal(undefined);
}); });
it(`supports [export default fn(){}] (default export)`, async () => { it(`supports [export default fn(){}] (default export)`, async () => {
mockProject([`export default x => x * 3`]); mockProject([`export default x => x * 3`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstResult = getEntry(queryResults[0]).result[0];
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstResult.exportSpecifiers).to.eql(['[default]']);
expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); expect(firstResult.source).to.equal(undefined);
expect(firstEntry.result[0].source).to.equal(undefined);
}); });
it(`supports [export {default as x} from 'y'] (default re-export)`, async () => { it(`supports [export {default as x} from 'y'] (default re-export)`, async () => {
@ -55,10 +54,9 @@ describe('Analyzer "find-exports"', () => {
"export { default as namedExport } from './file-with-default-export.js';", "export { default as namedExport } from './file-with-default-export.js';",
}); });
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstResult = getEntry(queryResults[0]).result[0];
const firstEntry = getEntry(queryResult); expect(firstResult).to.eql({
expect(firstEntry.result[0]).to.eql({
exportSpecifiers: ['[default]'], exportSpecifiers: ['[default]'],
source: undefined, source: undefined,
rootFileMap: [ rootFileMap: [
@ -69,10 +67,7 @@ describe('Analyzer "find-exports"', () => {
], ],
}); });
const secondEntry = getEntry(queryResult, 1); const secondEntry = getEntry(queryResults[0], 1);
expect(secondEntry.result[0].exportSpecifiers.length).to.equal(1);
expect(secondEntry.result[0].exportSpecifiers[0]).to.equal('namedExport');
expect(secondEntry.result[0].source).to.equal('./file-with-default-export.js');
expect(secondEntry.result[0]).to.eql({ expect(secondEntry.result[0]).to.eql({
exportSpecifiers: ['namedExport'], exportSpecifiers: ['namedExport'],
source: './file-with-default-export.js', source: './file-with-default-export.js',
@ -89,9 +84,8 @@ describe('Analyzer "find-exports"', () => {
it(`supports [import {x} from 'y'; export default x] (named re-export as default)`, async () => { it(`supports [import {x} from 'y'; export default x] (named re-export as default)`, async () => {
mockProject([`import {x} from 'y'; export default x;`]); mockProject([`import {x} from 'y'; export default x;`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1);
expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]');
expect(firstEntry.result[0].source).to.equal('y'); expect(firstEntry.result[0].source).to.equal('y');
@ -99,9 +93,8 @@ describe('Analyzer "find-exports"', () => {
it(`supports [import x from 'y'; export default x] (default re-export as default)`, async () => { it(`supports [import x from 'y'; export default x] (default re-export as default)`, async () => {
mockProject([`import x from 'y'; export default x;`]); mockProject([`import x from 'y'; export default x;`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1);
expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]');
expect(firstEntry.result[0].source).to.equal('y'); expect(firstEntry.result[0].source).to.equal('y');
@ -109,9 +102,8 @@ describe('Analyzer "find-exports"', () => {
it(`supports [export { x } from 'my/source'] (re-export named specifier)`, async () => { it(`supports [export { x } from 'my/source'] (re-export named specifier)`, async () => {
mockProject([`export { x } from 'my/source'`]); mockProject([`export { x } from 'my/source'`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1);
expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('x'); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('x');
expect(firstEntry.result[0].source).to.equal('my/source'); expect(firstEntry.result[0].source).to.equal('my/source');
@ -119,9 +111,8 @@ describe('Analyzer "find-exports"', () => {
it(`supports [export { x as y } from 'my/source'] (re-export renamed specifier)`, async () => { it(`supports [export { x as y } from 'my/source'] (re-export renamed specifier)`, async () => {
mockProject([`export { x as y } from 'my/source'`]); mockProject([`export { x as y } from 'my/source'`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1);
expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('y'); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('y');
expect(firstEntry.result[0].source).to.equal('my/source'); expect(firstEntry.result[0].source).to.equal('my/source');
@ -130,16 +121,15 @@ describe('Analyzer "find-exports"', () => {
it(`supports [export styles from './styles.css' assert { type: "css" }] (import assertions)`, async () => { it(`supports [export styles from './styles.css' assert { type: "css" }] (import assertions)`, async () => {
mockProject({ mockProject({
'./styles.css': '.block { display:block; };', './styles.css': '.block { display:block; };',
'./x.js': `export styles from './styles.css' assert { type: "css" };`, './x.js': `export { styles as default } from './styles.css' assert { type: "css" };`,
}); });
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1);
expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('styles'); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]');
expect(firstEntry.result[0].source).to.equal('./styles.css'); expect(firstEntry.result[0].source).to.equal('./styles.css');
expect(firstEntry.result[0].rootFileMap[0]).to.eql({ expect(firstEntry.result[0].rootFileMap[0]).to.eql({
currentFileSpecifier: 'styles', currentFileSpecifier: '[default]',
rootFile: { rootFile: {
file: './styles.css', file: './styles.css',
specifier: '[default]', specifier: '[default]',
@ -152,9 +142,8 @@ describe('Analyzer "find-exports"', () => {
'./styles.css': '.block { display:block; };', './styles.css': '.block { display:block; };',
'./x.js': `import styles from './styles.css' assert { type: "css" }; export default styles;`, './x.js': `import styles from './styles.css' assert { type: "css" }; export default styles;`,
}); });
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1);
expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]');
expect(firstEntry.result[0].source).to.equal('./styles.css'); expect(firstEntry.result[0].source).to.equal('./styles.css');
@ -169,9 +158,8 @@ describe('Analyzer "find-exports"', () => {
it(`stores meta info(local name) of renamed specifiers`, async () => { it(`stores meta info(local name) of renamed specifiers`, async () => {
mockProject([`export { x as y } from 'my/source'`]); mockProject([`export { x as y } from 'my/source'`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
// This info will be relevant later to identify 'transitive' relations // This info will be relevant later to identify 'transitive' relations
expect(firstEntry.result[0].localMap).to.eql([ expect(firstEntry.result[0].localMap).to.eql([
{ {
@ -183,9 +171,8 @@ describe('Analyzer "find-exports"', () => {
it(`supports [export { x, y } from 'my/source'] (multiple re-exported named specifiers)`, async () => { it(`supports [export { x, y } from 'my/source'] (multiple re-exported named specifiers)`, async () => {
mockProject([`export { x, y } from 'my/source'`]); mockProject([`export { x, y } from 'my/source'`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(2); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(2);
expect(firstEntry.result[0].exportSpecifiers).to.eql(['x', 'y']); expect(firstEntry.result[0].exportSpecifiers).to.eql(['x', 'y']);
expect(firstEntry.result[0].source).to.equal('my/source'); expect(firstEntry.result[0].source).to.equal('my/source');
@ -197,12 +184,10 @@ describe('Analyzer "find-exports"', () => {
'./src/inbetween.js': `export { OriginalComp as InBetweenComp } from './OriginalComp.js'`, './src/inbetween.js': `export { OriginalComp as InBetweenComp } from './OriginalComp.js'`,
'./index.js': `export { InBetweenComp as MyComp } from './src/inbetween.js'`, './index.js': `export { InBetweenComp as MyComp } from './src/inbetween.js'`,
}); });
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const secondEntry = getEntry(queryResults[0], 1);
const firstEntry = getEntry(queryResult); const thirdEntry = getEntry(queryResults[0], 2);
const secondEntry = getEntry(queryResult, 1);
const thirdEntry = getEntry(queryResult, 2);
expect(firstEntry.result[0].rootFileMap).to.eql([ expect(firstEntry.result[0].rootFileMap).to.eql([
{ {
@ -247,9 +232,8 @@ describe('Analyzer "find-exports"', () => {
export default ExtendRefDefault; export default ExtendRefDefault;
`, `,
}); });
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].rootFileMap).to.eql([ expect(firstEntry.result[0].rootFileMap).to.eql([
{ {
@ -265,9 +249,8 @@ describe('Analyzer "find-exports"', () => {
it(`correctly handles empty files`, async () => { it(`correctly handles empty files`, async () => {
// These can be encountered while scanning repos.. They should not break the code... // These can be encountered while scanning repos.. They should not break the code...
mockProject([`// some comment here...`]); mockProject([`// some comment here...`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers).to.eql(['[file]']); expect(firstEntry.result[0].exportSpecifiers).to.eql(['[file]']);
expect(firstEntry.result[0].source).to.equal(undefined); expect(firstEntry.result[0].source).to.equal(undefined);
}); });
@ -276,9 +259,8 @@ describe('Analyzer "find-exports"', () => {
describe('Export variable types', () => { describe('Export variable types', () => {
it(`classes`, async () => { it(`classes`, async () => {
mockProject([`export class X {}`]); mockProject([`export class X {}`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1);
expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('X'); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('X');
expect(firstEntry.result[0].source).to.be.undefined; expect(firstEntry.result[0].source).to.be.undefined;
@ -286,9 +268,8 @@ describe('Analyzer "find-exports"', () => {
it(`functions`, async () => { it(`functions`, async () => {
mockProject([`export function y() {}`]); mockProject([`export function y() {}`]);
await providence(findExportsQueryConfig, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const firstEntry = getEntry(queryResults[0]);
const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1);
expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('y'); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('y');
expect(firstEntry.result[0].source).to.be.undefined; expect(firstEntry.result[0].source).to.be.undefined;
@ -319,7 +300,9 @@ describe('Analyzer "find-exports"', () => {
}, },
); );
const findExportsCategoryQueryObj = QueryService.getQueryConfigFromAnalyzer('find-exports', { const findExportsCategoryQueryObj = await QueryService.getQueryConfigFromAnalyzer(
'find-exports',
{
metaConfig: { metaConfig: {
categoryConfig: [ categoryConfig: [
{ {
@ -332,9 +315,10 @@ describe('Analyzer "find-exports"', () => {
}, },
], ],
}, },
}); },
);
await providence(findExportsCategoryQueryObj, _providenceCfg); const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const [firstEntry, secondEntry, thirdEntry] = getEntries(queryResult); const [firstEntry, secondEntry, thirdEntry] = getEntries(queryResult);
expect(firstEntry.meta.categories).to.eql(['fooCategory']); expect(firstEntry.meta.categories).to.eql(['fooCategory']);

View file

@ -1,26 +1,28 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { providence } = require('../../../src/program/providence.js'); import { it } from 'mocha';
const { QueryService } = require('../../../src/program/core/QueryService.js'); import { providence } from '../../../src/program/providence.js';
const { setupAnalyzerTest } = require('../../../test-helpers/setup-analyzer-test.js'); import { QueryService } from '../../../src/program/core/QueryService.js';
const { mockProject, getEntry } = require('../../../test-helpers/mock-project-helpers.js'); import { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js';
import { mockProject, getEntry } from '../../../test-helpers/mock-project-helpers.js';
import FindImportsAnalyzer from '../../../src/program/analyzers/find-imports.js';
/** /**
* @typedef {import('../../../src/program/types/core').ProvidenceConfig} ProvidenceConfig * @typedef {import('../../../types/index.js').ProvidenceConfig} ProvidenceConfig
*/ */
const findImportsQueryConfig = QueryService.getQueryConfigFromAnalyzer('find-imports'); setupAnalyzerTest();
describe('Analyzer "find-imports"', async () => {
const findImportsQueryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer);
/** @type {Partial<ProvidenceConfig>} */ /** @type {Partial<ProvidenceConfig>} */
const _providenceCfg = { const _providenceCfg = {
targetProjectPaths: ['/fictional/project'], // defined in mockProject targetProjectPaths: ['/fictional/project'], // defined in mockProject
}; };
describe('Analyzer "find-imports"', () => {
const queryResults = setupAnalyzerTest();
describe('Import notations', () => { describe('Import notations', () => {
it(`supports [import 'imported/source'] (no specifiers)`, async () => { it(`supports [import 'imported/source'] (no specifiers)`, async () => {
mockProject([`import 'imported/source'`]); mockProject([`import 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers).to.eql(['[file]']); expect(firstEntry.result[0].importSpecifiers).to.eql(['[file]']);
@ -29,7 +31,7 @@ describe('Analyzer "find-imports"', () => {
it(`supports [import x from 'imported/source'] (default specifier)`, async () => { it(`supports [import x from 'imported/source'] (default specifier)`, async () => {
mockProject([`import x from 'imported/source'`]); mockProject([`import x from 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]');
@ -38,7 +40,7 @@ describe('Analyzer "find-imports"', () => {
it(`supports [import { x } from 'imported/source'] (named specifier)`, async () => { it(`supports [import { x } from 'imported/source'] (named specifier)`, async () => {
mockProject([`import { x } from 'imported/source'`]); mockProject([`import { x } from 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x');
@ -48,7 +50,7 @@ describe('Analyzer "find-imports"', () => {
it(`supports [import { x, y } from 'imported/source'] (multiple named specifiers)`, async () => { it(`supports [import { x, y } from 'imported/source'] (multiple named specifiers)`, async () => {
mockProject([`import { x, y } from 'imported/source'`]); mockProject([`import { x, y } from 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x');
@ -59,7 +61,7 @@ describe('Analyzer "find-imports"', () => {
it(`supports [import x, { y, z } from 'imported/source'] (default and named specifiers)`, async () => { it(`supports [import x, { y, z } from 'imported/source'] (default and named specifiers)`, async () => {
mockProject([`import x, { y, z } from 'imported/source'`]); mockProject([`import x, { y, z } from 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]');
@ -70,7 +72,7 @@ describe('Analyzer "find-imports"', () => {
it(`supports [import { x as y } from 'imported/source'] (renamed specifiers)`, async () => { it(`supports [import { x as y } from 'imported/source'] (renamed specifiers)`, async () => {
mockProject([`import { x as y } from 'imported/source'`]); mockProject([`import { x as y } from 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x');
@ -78,41 +80,41 @@ describe('Analyzer "find-imports"', () => {
it(`supports [import * as all from 'imported/source'] (namespace specifiers)`, async () => { it(`supports [import * as all from 'imported/source'] (namespace specifiers)`, async () => {
mockProject([`import * as all from 'imported/source'`]); mockProject([`import * as all from 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[*]'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[*]');
}); });
describe('Reexports', () => { describe('Reexports', () => {
it(`supports [export { x } from 'imported/source'] (reexported named specifiers)`, async () => { it(`supports [export { x } from 'imported/source'] (re-exported named specifiers)`, async () => {
mockProject([`export { x } from 'imported/source'`]); mockProject([`export { x } from 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x');
}); });
it(`supports [export { x as y } from 'imported/source'] (reexported renamed specifiers)`, async () => { it(`supports [export { x as y } from 'imported/source'] (re-exported renamed specifiers)`, async () => {
mockProject([`export { x as y } from 'imported/source'`]); mockProject([`export { x as y } from 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x');
}); });
// maybe in the future... needs experimental babel flag "exportDefaultFrom" // maybe in the future... needs experimental babel flag "exportDefaultFrom"
it.skip(`supports [export x from 'imported/source'] (reexported default specifiers)`, async () => { it.skip(`supports [export x from 'imported/source'] (re-exported default specifiers)`, async () => {
mockProject([`export x from 'imported/source'`]); mockProject([`export x from 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x');
}); });
it(`supports [export * as x from 'imported/source'] (reexported namespace specifiers)`, async () => { it(`supports [export * as x from 'imported/source'] (re-exported namespace specifiers)`, async () => {
mockProject([`export * as x from 'imported/source'`]); mockProject([`export * as x from 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[*]'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[*]');
@ -122,7 +124,7 @@ describe('Analyzer "find-imports"', () => {
// Currently only supported for find-exports. For now not needed... // Currently only supported for find-exports. For now not needed...
it.skip(`stores meta info(local name) of renamed specifiers`, async () => { it.skip(`stores meta info(local name) of renamed specifiers`, async () => {
mockProject([`import { x as y } from 'imported/source'`]); mockProject([`import { x as y } from 'imported/source'`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
// This info will be relevant later to identify transitive relations // This info will be relevant later to identify transitive relations
@ -134,7 +136,7 @@ describe('Analyzer "find-imports"', () => {
it(`supports [import('my/source')] (dynamic imports)`, async () => { it(`supports [import('my/source')] (dynamic imports)`, async () => {
mockProject([`import('my/source')`]); mockProject([`import('my/source')`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]');
@ -150,7 +152,7 @@ describe('Analyzer "find-imports"', () => {
import(pathReference); import(pathReference);
`, `,
]); ]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]');
@ -161,7 +163,7 @@ describe('Analyzer "find-imports"', () => {
// import styles from "./styles.css" assert { type: "css" }; // import styles from "./styles.css" assert { type: "css" };
it(`supports [import styles from "@css/lib/styles.css" assert { type: "css" }] (import assertions)`, async () => { it(`supports [import styles from "@css/lib/styles.css" assert { type: "css" }] (import assertions)`, async () => {
mockProject([`import styles from "@css/lib/styles.css" assert { type: "css" };`]); mockProject([`import styles from "@css/lib/styles.css" assert { type: "css" };`]);
await providence(findImportsQueryConfig, _providenceCfg); const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]');
@ -170,8 +172,10 @@ describe('Analyzer "find-imports"', () => {
}); });
it(`supports [export styles from "@css/lib/styles.css" assert { type: "css" }] (import assertions)`, async () => { it(`supports [export styles from "@css/lib/styles.css" assert { type: "css" }] (import assertions)`, async () => {
mockProject([`export styles from "@css/lib/styles.css" assert { type: "css" };`]); mockProject([
await providence(findImportsQueryConfig, _providenceCfg); `export { styles as default } from "@css/lib/styles.css" assert { type: "css" };`,
]);
const queryResults = await providence(findImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]');
@ -182,7 +186,7 @@ describe('Analyzer "find-imports"', () => {
describe('Filter out false positives', () => { describe('Filter out false positives', () => {
it(`doesn't support [object.import('my/source')] (import method members)`, async () => { it(`doesn't support [object.import('my/source')] (import method members)`, async () => {
mockProject([`object.import('my/source')`]); mockProject([`object.import('my/source')`]);
await providence(findImportsQueryConfig, { const queryResults = await providence(findImportsQueryConfig, {
targetProjectPaths: ['/fictional/project'], // defined in mockProject targetProjectPaths: ['/fictional/project'], // defined in mockProject
}); });
const queryResult = queryResults[0]; const queryResult = queryResults[0];
@ -209,7 +213,7 @@ describe('Analyzer "find-imports"', () => {
import '../../internal/source'; import '../../internal/source';
`, `,
]); ]);
await providence(findImportsQueryConfig, { ..._providenceCfg }); const queryResults = await providence(findImportsQueryConfig, { ..._providenceCfg });
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers.length).to.equal(1); expect(firstEntry.result[0].importSpecifiers.length).to.equal(1);
@ -219,7 +223,7 @@ describe('Analyzer "find-imports"', () => {
}); });
it('normalizes source paths', async () => { it('normalizes source paths', async () => {
const queryConfig = QueryService.getQueryConfigFromAnalyzer('find-imports', { const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, {
keepInternalSources: true, keepInternalSources: true,
}); });
mockProject({ mockProject({
@ -232,7 +236,7 @@ describe('Analyzer "find-imports"', () => {
'./internal/source/x.js': '', './internal/source/x.js': '',
'./index.js': '', './index.js': '',
}); });
await providence(queryConfig, _providenceCfg); const queryResults = await providence(queryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
expect(firstEntry.result[0].importSpecifiers.length).to.equal(1); expect(firstEntry.result[0].importSpecifiers.length).to.equal(1);
@ -250,7 +254,7 @@ describe('Analyzer "find-imports"', () => {
describe('Options', () => { describe('Options', () => {
it('"keepInternalSources"', async () => { it('"keepInternalSources"', async () => {
const queryConfig = QueryService.getQueryConfigFromAnalyzer('find-imports', { const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, {
keepInternalSources: true, keepInternalSources: true,
}); });
mockProject([ mockProject([
@ -262,7 +266,7 @@ describe('Analyzer "find-imports"', () => {
import '../../internal/source'; import '../../internal/source';
`, `,
]); ]);
await providence(queryConfig, _providenceCfg); const queryResults = await providence(queryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
@ -277,11 +281,11 @@ describe('Analyzer "find-imports"', () => {
// Post processors for whole result // Post processors for whole result
it('"keepOriginalSourceExtensions"', async () => { it('"keepOriginalSourceExtensions"', async () => {
const queryConfig = QueryService.getQueryConfigFromAnalyzer('find-imports', { const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, {
keepOriginalSourceExtensions: true, keepOriginalSourceExtensions: true,
}); });
mockProject([`import '@external/source.js'`, `import '@external/source';`]); mockProject([`import '@external/source.js'`, `import '@external/source';`]);
await providence(queryConfig, _providenceCfg); const queryResults = await providence(queryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const firstEntry = getEntry(queryResult); const firstEntry = getEntry(queryResult);
@ -295,7 +299,7 @@ describe('Analyzer "find-imports"', () => {
// but only without loss of information and once depending analyzers (match-imports and // but only without loss of information and once depending analyzers (match-imports and
// match-subclasses) are made compatible. // match-subclasses) are made compatible.
it.skip('"sortBySpecifier"', async () => { it.skip('"sortBySpecifier"', async () => {
const queryConfig = QueryService.getQueryConfigFromAnalyzer('find-imports', { const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, {
sortBySpecifier: true, sortBySpecifier: true,
}); });
mockProject( mockProject(
@ -305,7 +309,7 @@ describe('Analyzer "find-imports"', () => {
], ],
{ filePaths: ['./file1.js', './file2.js'] }, { filePaths: ['./file1.js', './file2.js'] },
); );
await providence(queryConfig, _providenceCfg); const queryResults = await providence(queryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
/** /**

View file

@ -1,25 +1,24 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { default: traverse } = require('@babel/traverse'); import { it } from 'mocha';
const { import babelTraverse from '@babel/traverse';
import {
trackDownIdentifier, trackDownIdentifier,
trackDownIdentifierFromScope, trackDownIdentifierFromScope,
} = require('../../../../src/program/analyzers/helpers/track-down-identifier.js'); } from '../../../../src/program/analyzers/helpers/track-down-identifier.js';
const { AstService } = require('../../../../src/program/core/AstService.js'); import { AstService } from '../../../../src/program/core/AstService.js';
const { import {
mockProject, mockProject,
restoreMockedProjects, restoreMockedProjects,
} = require('../../../../test-helpers/mock-project-helpers.js'); } from '../../../../test-helpers/mock-project-helpers.js';
const { memoizeConfig } = require('../../../../src/program/utils/memoize.js'); import { setupAnalyzerTest } from '../../../../test-helpers/setup-analyzer-test.js';
/**
* @typedef {import('@babel/traverse').NodePath} NodePath
*/
setupAnalyzerTest();
describe('trackdownIdentifier', () => { describe('trackdownIdentifier', () => {
beforeEach(() => {
memoizeConfig.isCacheDisabled = true;
});
afterEach(() => {
memoizeConfig.isCacheDisabled = false;
restoreMockedProjects();
});
it(`tracks down identifier to root file (file that declares identifier)`, async () => { it(`tracks down identifier to root file (file that declares identifier)`, async () => {
mockProject( mockProject(
{ {
@ -306,15 +305,17 @@ describe('trackDownIdentifierFromScope', () => {
const identifierNameInScope = 'MyClass'; const identifierNameInScope = 'MyClass';
const fullCurrentFilePath = '/my/project//src/declarationOfMyClass.js'; const fullCurrentFilePath = '/my/project//src/declarationOfMyClass.js';
const projectPath = '/my/project'; const projectPath = '/my/project';
/** @type {NodePath} */
let astPath; let astPath;
traverse(ast, { babelTraverse.default(ast, {
ClassDeclaration(path) { ClassDeclaration(path) {
astPath = path; astPath = path;
}, },
}); });
const rootFile = await trackDownIdentifierFromScope( const rootFile = await trackDownIdentifierFromScope(
// @ts-ignore
astPath, astPath,
identifierNameInScope, identifierNameInScope,
fullCurrentFilePath, fullCurrentFilePath,
@ -349,15 +350,17 @@ describe('trackDownIdentifierFromScope', () => {
const identifierNameInScope = 'MyClass'; const identifierNameInScope = 'MyClass';
const fullCurrentFilePath = '/my/project/internal.js'; const fullCurrentFilePath = '/my/project/internal.js';
const projectPath = '/my/project'; const projectPath = '/my/project';
/** @type {NodePath} */
let astPath; let astPath;
traverse(ast, { babelTraverse.default(ast, {
ImportDeclaration(path) { ImportDeclaration(path) {
astPath = path; astPath = path;
}, },
}); });
const rootFile = await trackDownIdentifierFromScope( const rootFile = await trackDownIdentifierFromScope(
// @ts-ignore
astPath, astPath,
identifierNameInScope, identifierNameInScope,
fullCurrentFilePath, fullCurrentFilePath,
@ -389,15 +392,17 @@ describe('trackDownIdentifierFromScope', () => {
const identifierNameInScope = 'El1'; const identifierNameInScope = 'El1';
const fullCurrentFilePath = '/my/project/internal.js'; const fullCurrentFilePath = '/my/project/internal.js';
const projectPath = '/my/project'; const projectPath = '/my/project';
/** @type {NodePath} */
let astPath; let astPath;
traverse(ast, { babelTraverse.default(ast, {
ClassDeclaration(path) { ClassDeclaration(path) {
astPath = path; astPath = path;
}, },
}); });
const rootFile = await trackDownIdentifierFromScope( const rootFile = await trackDownIdentifierFromScope(
// @ts-ignore
astPath, astPath,
identifierNameInScope, identifierNameInScope,
fullCurrentFilePath, fullCurrentFilePath,

View file

@ -1,12 +1,24 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { providence } = require('../../../src/program/providence.js'); import { it } from 'mocha';
const { QueryService } = require('../../../src/program/core/QueryService.js'); import { providence } from '../../../src/program/providence.js';
const FindExportsAnalyzer = require('../../../src/program/analyzers/find-exports.js'); import { QueryService } from '../../../src/program/core/QueryService.js';
const FindImportsAnalyzer = require('../../../src/program/analyzers/find-imports.js'); import FindExportsAnalyzer from '../../../src/program/analyzers/find-exports.js';
const { setupAnalyzerTest } = require('../../../test-helpers/setup-analyzer-test.js'); import FindImportsAnalyzer from '../../../src/program/analyzers/find-imports.js';
const { mockTargetAndReferenceProject } = require('../../../test-helpers/mock-project-helpers.js'); import { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js';
import { mockTargetAndReferenceProject } from '../../../test-helpers/mock-project-helpers.js';
import MatchImportsAnalyzer from '../../../src/program/analyzers/match-imports.js';
const matchImportsQueryConfig = QueryService.getQueryConfigFromAnalyzer('match-imports'); /**
* @typedef {import('../../../types/index.js').ProvidenceConfig} ProvidenceConfig
*/
setupAnalyzerTest();
//
describe('Analyzer "match-imports"', async () => {
const matchImportsQueryConfig = await QueryService.getQueryConfigFromAnalyzer(
MatchImportsAnalyzer,
);
/** @type {Partial<ProvidenceConfig>} */
const _providenceCfg = { const _providenceCfg = {
targetProjectPaths: ['/importing/target/project'], targetProjectPaths: ['/importing/target/project'],
referenceProjectPaths: ['/importing/target/project/node_modules/exporting-ref-project'], referenceProjectPaths: ['/importing/target/project/node_modules/exporting-ref-project'],
@ -197,9 +209,6 @@ const expectedMatchesOutput = [
}, },
]; ];
describe('Analyzer "match-imports"', () => {
const queryResults = setupAnalyzerTest();
function testMatchedEntry(targetExportedId, queryResult, importedByFiles = []) { function testMatchedEntry(targetExportedId, queryResult, importedByFiles = []) {
const matchedEntry = queryResult.queryOutput.find( const matchedEntry = queryResult.queryOutput.find(
r => r.exportSpecifier.id === targetExportedId, r => r.exportSpecifier.id === targetExportedId,
@ -229,7 +238,8 @@ describe('Analyzer "match-imports"', () => {
files: [{ file: './index.js', code: `import myFn from 'ref/direct.js';` }], files: [{ file: './index.js', code: `import myFn from 'ref/direct.js';` }],
}; };
mockTargetAndReferenceProject(targetProject, refProject); mockTargetAndReferenceProject(targetProject, refProject);
await providence(matchImportsQueryConfig, {
const queryResults = await providence(matchImportsQueryConfig, {
targetProjectPaths: [targetProject.path], targetProjectPaths: [targetProject.path],
referenceProjectPaths: [refProject.path], referenceProjectPaths: [refProject.path],
}); });
@ -262,7 +272,7 @@ describe('Analyzer "match-imports"', () => {
files: [{ file: './index.js', code: `import { x } from 'ref/indirect.js';` }], files: [{ file: './index.js', code: `import { x } from 'ref/indirect.js';` }],
}; };
mockTargetAndReferenceProject(targetProject, refProject); mockTargetAndReferenceProject(targetProject, refProject);
await providence(matchImportsQueryConfig, { const queryResults = await providence(matchImportsQueryConfig, {
targetProjectPaths: [targetProject.path], targetProjectPaths: [targetProject.path],
referenceProjectPaths: [refProject.path], referenceProjectPaths: [refProject.path],
}); });
@ -294,7 +304,7 @@ describe('Analyzer "match-imports"', () => {
files: [{ file: './index.js', code: `import * as xy from 'ref/namespaced.js';` }], files: [{ file: './index.js', code: `import * as xy from 'ref/namespaced.js';` }],
}; };
mockTargetAndReferenceProject(targetProject, refProject); mockTargetAndReferenceProject(targetProject, refProject);
await providence(matchImportsQueryConfig, { const queryResults = await providence(matchImportsQueryConfig, {
targetProjectPaths: [targetProject.path], targetProjectPaths: [targetProject.path],
referenceProjectPaths: [refProject.path], referenceProjectPaths: [refProject.path],
}); });
@ -333,7 +343,7 @@ describe('Analyzer "match-imports"', () => {
describe('Inside small example project', () => { describe('Inside small example project', () => {
it(`identifies all direct export specifiers consumed by "importing-target-project"`, async () => { it(`identifies all direct export specifiers consumed by "importing-target-project"`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchImportsQueryConfig, _providenceCfg); const queryResults = await providence(matchImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expectedExportIdsDirect.forEach(directId => { expectedExportIdsDirect.forEach(directId => {
expect( expect(
@ -346,7 +356,7 @@ describe('Analyzer "match-imports"', () => {
it(`identifies all indirect export specifiers consumed by "importing-target-project"`, async () => { it(`identifies all indirect export specifiers consumed by "importing-target-project"`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchImportsQueryConfig, _providenceCfg); const queryResults = await providence(matchImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expectedExportIdsIndirect.forEach(indirectId => { expectedExportIdsIndirect.forEach(indirectId => {
expect( expect(
@ -359,7 +369,7 @@ describe('Analyzer "match-imports"', () => {
it(`matches namespaced specifiers consumed by "importing-target-project"`, async () => { it(`matches namespaced specifiers consumed by "importing-target-project"`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchImportsQueryConfig, _providenceCfg); const queryResults = await providence(matchImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expectedExportIdsNamespaced.forEach(exportedSpecifierId => { expectedExportIdsNamespaced.forEach(exportedSpecifierId => {
expect( expect(
@ -394,7 +404,7 @@ describe('Analyzer "match-imports"', () => {
files: [{ file: './index.js', code: `import myFn from 'ref/direct.js';` }], files: [{ file: './index.js', code: `import myFn from 'ref/direct.js';` }],
}; };
mockTargetAndReferenceProject(targetProject, refProject); mockTargetAndReferenceProject(targetProject, refProject);
await providence(matchImportsQueryConfig, { const queryResults = await providence(matchImportsQueryConfig, {
targetProjectPaths: [targetProject.path], targetProjectPaths: [targetProject.path],
referenceProjectPaths: [refProject.path], referenceProjectPaths: [refProject.path],
}); });
@ -419,7 +429,7 @@ describe('Analyzer "match-imports"', () => {
], ],
}; };
mockTargetAndReferenceProject(targetProject, refProject); mockTargetAndReferenceProject(targetProject, refProject);
await providence(matchImportsQueryConfig, { const queryResults = await providence(matchImportsQueryConfig, {
targetProjectPaths: [targetProject.path], targetProjectPaths: [targetProject.path],
referenceProjectPaths: [refProject.path], referenceProjectPaths: [refProject.path],
}); });
@ -460,7 +470,7 @@ describe('Analyzer "match-imports"', () => {
], ],
}; };
mockTargetAndReferenceProject(targetProject, refProject); mockTargetAndReferenceProject(targetProject, refProject);
await providence(matchImportsQueryConfig, { const queryResults = await providence(matchImportsQueryConfig, {
targetProjectPaths: [targetProject.path], targetProjectPaths: [targetProject.path],
referenceProjectPaths: [refProject.path], referenceProjectPaths: [refProject.path],
}); });
@ -478,7 +488,7 @@ describe('Analyzer "match-imports"', () => {
describe('Inside small example project', () => { describe('Inside small example project', () => {
it(`produces a list of all matches, sorted by project`, async () => { it(`produces a list of all matches, sorted by project`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchImportsQueryConfig, _providenceCfg); const queryResults = await providence(matchImportsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expectedExportIdsDirect.forEach(targetId => { expectedExportIdsDirect.forEach(targetId => {
@ -502,11 +512,14 @@ describe('Analyzer "match-imports"', () => {
targetProjectPath: referenceProject.path, targetProjectPath: referenceProject.path,
}); });
const matchImportsQueryConfigExt = QueryService.getQueryConfigFromAnalyzer('match-imports', { const matchImportsQueryConfigExt = await QueryService.getQueryConfigFromAnalyzer(
MatchImportsAnalyzer,
{
targetProjectResult: findImportsResult, targetProjectResult: findImportsResult,
referenceProjectResult: findExportsResult, referenceProjectResult: findExportsResult,
}); },
await providence(matchImportsQueryConfigExt, _providenceCfg); );
const queryResults = await providence(matchImportsQueryConfigExt, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expectedExportIdsDirect.forEach(targetId => { expectedExportIdsDirect.forEach(targetId => {

View file

@ -1,12 +1,18 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { providence } = require('../../../src/program/providence.js'); import { it } from 'mocha';
const { QueryService } = require('../../../src/program/core/QueryService.js'); import { providence } from '../../../src/program/providence.js';
const { setupAnalyzerTest } = require('../../../test-helpers/setup-analyzer-test.js'); import { QueryService } from '../../../src/program/core/QueryService.js';
const { mockTargetAndReferenceProject } = require('../../../test-helpers/mock-project-helpers.js'); import { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js';
import { mockTargetAndReferenceProject } from '../../../test-helpers/mock-project-helpers.js';
import MatchPathsAnalyzer from '../../../src/program/analyzers/match-paths.js';
describe('Analyzer "match-paths"', () => { /**
const queryResults = setupAnalyzerTest(); * @typedef {import('../../../types/index.js').ProvidenceConfig} ProvidenceConfig
*/
setupAnalyzerTest();
describe('Analyzer "match-paths"', async () => {
const referenceProject = { const referenceProject = {
path: '/importing/target/project/node_modules/reference-project', path: '/importing/target/project/node_modules/reference-project',
name: 'reference-project', name: 'reference-project',
@ -108,7 +114,8 @@ describe('Analyzer "match-paths"', () => {
], ],
}; };
const matchPathsQueryConfig = QueryService.getQueryConfigFromAnalyzer('match-paths'); const matchPathsQueryConfig = await QueryService.getQueryConfigFromAnalyzer(MatchPathsAnalyzer);
/** @type {Partial<ProvidenceConfig>} */
const _providenceCfg = { const _providenceCfg = {
targetProjectPaths: [searchTargetProject.path], targetProjectPaths: [searchTargetProject.path],
referenceProjectPaths: [referenceProject.path], referenceProjectPaths: [referenceProject.path],
@ -179,7 +186,7 @@ describe('Analyzer "match-paths"', () => {
it(`outputs an array result with from/to classes and paths`, async () => { it(`outputs an array result with from/to classes and paths`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput).to.eql(expectedMatches); expect(queryResult.queryOutput).to.eql(expectedMatches);
}); });
@ -222,7 +229,7 @@ describe('Analyzer "match-paths"', () => {
it(`identifies all "from" and "to" classes`, async () => { it(`identifies all "from" and "to" classes`, async () => {
mockTargetAndReferenceProject(targetProj, refProj); mockTargetAndReferenceProject(targetProj, refProj);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput[0].variable.from).to.equal('RefClass'); expect(queryResult.queryOutput[0].variable.from).to.equal('RefClass');
expect(queryResult.queryOutput[0].variable.to).to.equal('TargetClass'); expect(queryResult.queryOutput[0].variable.to).to.equal('TargetClass');
@ -230,7 +237,7 @@ describe('Analyzer "match-paths"', () => {
it(`identifies all "from" and "to" paths`, async () => { it(`identifies all "from" and "to" paths`, async () => {
mockTargetAndReferenceProject(targetProj, refProj); mockTargetAndReferenceProject(targetProj, refProj);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput[0].variable.paths[0]).to.eql({ expect(queryResult.queryOutput[0].variable.paths[0]).to.eql({
from: './index.js', from: './index.js',
@ -254,7 +261,7 @@ describe('Analyzer "match-paths"', () => {
it(`gives back "to" path closest to root`, async () => { it(`gives back "to" path closest to root`, async () => {
mockTargetAndReferenceProject(targetProjWithMultipleExports, refProj); mockTargetAndReferenceProject(targetProjWithMultipleExports, refProj);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput[0].variable.paths[0]).to.eql({ expect(queryResult.queryOutput[0].variable.paths[0]).to.eql({
from: './index.js', from: './index.js',
@ -287,7 +294,7 @@ describe('Analyzer "match-paths"', () => {
], ],
}; };
mockTargetAndReferenceProject(targetProjWithMultipleExportsAndMainEntry, refProj); mockTargetAndReferenceProject(targetProjWithMultipleExportsAndMainEntry, refProj);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput[0].variable.paths[0]).to.eql({ expect(queryResult.queryOutput[0].variable.paths[0]).to.eql({
from: './index.js', from: './index.js',
@ -298,7 +305,7 @@ describe('Analyzer "match-paths"', () => {
it(`prefixes project paths`, async () => { it(`prefixes project paths`, async () => {
mockTargetAndReferenceProject(targetProj, refProj); mockTargetAndReferenceProject(targetProj, refProj);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const unprefixedPaths = queryResult.queryOutput[0].variable.paths[0]; const unprefixedPaths = queryResult.queryOutput[0].variable.paths[0];
expect(unprefixedPaths).to.eql({ from: './index.js', to: './target-src/TargetClass.js' }); expect(unprefixedPaths).to.eql({ from: './index.js', to: './target-src/TargetClass.js' });
@ -327,7 +334,7 @@ describe('Analyzer "match-paths"', () => {
], ],
}; };
mockTargetAndReferenceProject(targetProjMultipleTargetExtensions, refProj); mockTargetAndReferenceProject(targetProjMultipleTargetExtensions, refProj);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput[0].variable.paths[0]).to.eql({ expect(queryResult.queryOutput[0].variable.paths[0]).to.eql({
from: './index.js', from: './index.js',
@ -395,10 +402,13 @@ describe('Analyzer "match-paths"', () => {
], ],
}; };
mockTargetAndReferenceProject(targetProjMultipleTargetExtensions, refProj); mockTargetAndReferenceProject(targetProjMultipleTargetExtensions, refProj);
const matchPathsQueryConfigFilter = QueryService.getQueryConfigFromAnalyzer('match-paths', { const matchPathsQueryConfigFilter = await QueryService.getQueryConfigFromAnalyzer(
MatchPathsAnalyzer,
{
prefix: { from: 'ref', to: 'target' }, prefix: { from: 'ref', to: 'target' },
}); },
await providence(matchPathsQueryConfigFilter, _providenceCfg); );
const queryResults = await providence(matchPathsQueryConfigFilter, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput[0].variable.paths[0]).to.eql({ expect(queryResult.queryOutput[0].variable.paths[0]).to.eql({
from: './index.js', from: './index.js',
@ -503,7 +513,7 @@ describe('Analyzer "match-paths"', () => {
it(`outputs an array result with from/to tag names and paths`, async () => { it(`outputs an array result with from/to tag names and paths`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput[0].tag).to.eql(expectedMatches[0]); expect(queryResult.queryOutput[0].tag).to.eql(expectedMatches[0]);
expect(queryResult.queryOutput[1].tag).to.eql(expectedMatches[1]); expect(queryResult.queryOutput[1].tag).to.eql(expectedMatches[1]);
@ -563,7 +573,7 @@ describe('Analyzer "match-paths"', () => {
referenceProjectPaths: ['/their-components'], referenceProjectPaths: ['/their-components'],
}; };
await providence( const queryResults = await providence(
{ ...matchPathsQueryConfig, prefix: { from: 'their', to: 'my' } }, { ...matchPathsQueryConfig, prefix: { from: 'their', to: 'my' } },
providenceCfg, providenceCfg,
); );
@ -587,7 +597,7 @@ describe('Analyzer "match-paths"', () => {
describe('Features', () => { describe('Features', () => {
it(`identifies all "from" and "to" tagnames`, async () => { it(`identifies all "from" and "to" tagnames`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput[0].tag.from).to.equal('el-1'); expect(queryResult.queryOutput[0].tag.from).to.equal('el-1');
expect(queryResult.queryOutput[0].tag.to).to.equal('extended-el-1'); expect(queryResult.queryOutput[0].tag.to).to.equal('extended-el-1');
@ -595,7 +605,7 @@ describe('Analyzer "match-paths"', () => {
it(`identifies all "from" and "to" paths`, async () => { it(`identifies all "from" and "to" paths`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput[0].tag.paths[0]).to.eql({ expect(queryResult.queryOutput[0].tag.paths[0]).to.eql({
from: './customelementDefinitions.js', from: './customelementDefinitions.js',
@ -605,7 +615,7 @@ describe('Analyzer "match-paths"', () => {
it(`prefixes project paths`, async () => { it(`prefixes project paths`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput[0].tag.paths[1]).to.eql({ expect(queryResult.queryOutput[0].tag.paths[1]).to.eql({
from: 'reference-project/customelementDefinitions.js', from: 'reference-project/customelementDefinitions.js',
@ -724,7 +734,7 @@ describe('Analyzer "match-paths"', () => {
it(`outputs a "name", "variable" and "tag" entry`, async () => { it(`outputs a "name", "variable" and "tag" entry`, async () => {
mockTargetAndReferenceProject(searchTargetProjectFull, referenceProjectFull); mockTargetAndReferenceProject(searchTargetProjectFull, referenceProjectFull);
await providence(matchPathsQueryConfig, _providenceCfg); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expect(queryResult.queryOutput).to.eql(expectedMatchesFull); expect(queryResult.queryOutput).to.eql(expectedMatchesFull);
}); });

View file

@ -1,9 +1,18 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { providence } = require('../../../src/program/providence.js'); import { it } from 'mocha';
const { QueryService } = require('../../../src/program/core/QueryService.js'); import { providence } from '../../../src/program/providence.js';
const { mockTargetAndReferenceProject } = require('../../../test-helpers/mock-project-helpers.js'); import { QueryService } from '../../../src/program/core/QueryService.js';
const { setupAnalyzerTest } = require('../../../test-helpers/setup-analyzer-test.js'); import { mockTargetAndReferenceProject } from '../../../test-helpers/mock-project-helpers.js';
import { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js';
import MatchSubclassesAnalyzer from '../../../src/program/analyzers/match-subclasses.js';
/**
* @typedef {import('../../../types/index.js').ProvidenceConfig} ProvidenceConfig
*/
setupAnalyzerTest();
describe('Analyzer "match-subclasses"', async () => {
// 1. Reference input data // 1. Reference input data
const referenceProject = { const referenceProject = {
path: '/importing/target/project/node_modules/exporting-ref-project', path: '/importing/target/project/node_modules/exporting-ref-project',
@ -75,7 +84,10 @@ const searchTargetProject = {
], ],
}; };
const matchSubclassesQueryConfig = QueryService.getQueryConfigFromAnalyzer('match-subclasses'); const matchSubclassesQueryConfig = await QueryService.getQueryConfigFromAnalyzer(
MatchSubclassesAnalyzer,
);
/** @type {Partial<ProvidenceConfig>} */
const _providenceCfg = { const _providenceCfg = {
targetProjectPaths: [searchTargetProject.path], targetProjectPaths: [searchTargetProject.path],
referenceProjectPaths: [referenceProject.path], referenceProjectPaths: [referenceProject.path],
@ -122,11 +134,6 @@ const expectedMatchesOutput = [
}, },
]; ];
// eslint-disable-next-line no-shadow
describe('Analyzer "match-subclasses"', () => {
const queryResults = setupAnalyzerTest();
describe('Match Features', () => { describe('Match Features', () => {
it(`identifies all directly imported class extensions`, async () => { it(`identifies all directly imported class extensions`, async () => {
const refProject = { const refProject = {
@ -149,7 +156,7 @@ describe('Analyzer "match-subclasses"', () => {
], ],
}; };
mockTargetAndReferenceProject(targetProject, refProject); mockTargetAndReferenceProject(targetProject, refProject);
await providence(matchSubclassesQueryConfig, { const queryResults = await providence(matchSubclassesQueryConfig, {
targetProjectPaths: [targetProject.path], targetProjectPaths: [targetProject.path],
referenceProjectPaths: [refProject.path], referenceProjectPaths: [refProject.path],
}); });
@ -201,7 +208,7 @@ describe('Analyzer "match-subclasses"', () => {
], ],
}; };
mockTargetAndReferenceProject(targetProject, refProject); mockTargetAndReferenceProject(targetProject, refProject);
await providence(matchSubclassesQueryConfig, { const queryResults = await providence(matchSubclassesQueryConfig, {
targetProjectPaths: [targetProject.path], targetProjectPaths: [targetProject.path],
referenceProjectPaths: [refProject.path], referenceProjectPaths: [refProject.path],
}); });
@ -255,7 +262,7 @@ describe('Analyzer "match-subclasses"', () => {
], ],
}; };
mockTargetAndReferenceProject(targetProject, refProject); mockTargetAndReferenceProject(targetProject, refProject);
await providence(matchSubclassesQueryConfig, { const queryResults = await providence(matchSubclassesQueryConfig, {
targetProjectPaths: [targetProject.path], targetProjectPaths: [targetProject.path],
referenceProjectPaths: [refProject.path], referenceProjectPaths: [refProject.path],
}); });
@ -289,7 +296,7 @@ describe('Analyzer "match-subclasses"', () => {
describe('Inside small example project', () => { describe('Inside small example project', () => {
it(`identifies all indirect export specifiers consumed by "importing-target-project"`, async () => { it(`identifies all indirect export specifiers consumed by "importing-target-project"`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchSubclassesQueryConfig, _providenceCfg); const queryResults = await providence(matchSubclassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expectedExportIdsIndirect.forEach(indirectId => { expectedExportIdsIndirect.forEach(indirectId => {
expect( expect(
@ -302,7 +309,7 @@ describe('Analyzer "match-subclasses"', () => {
it(`identifies all direct export specifiers consumed by "importing-target-project"`, async () => { it(`identifies all direct export specifiers consumed by "importing-target-project"`, async () => {
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchSubclassesQueryConfig, _providenceCfg); const queryResults = await providence(matchSubclassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expectedExportIdsDirect.forEach(directId => { expectedExportIdsDirect.forEach(directId => {
expect( expect(
@ -336,7 +343,7 @@ describe('Analyzer "match-subclasses"', () => {
} }
mockTargetAndReferenceProject(searchTargetProject, referenceProject); mockTargetAndReferenceProject(searchTargetProject, referenceProject);
await providence(matchSubclassesQueryConfig, _providenceCfg); const queryResults = await providence(matchSubclassesQueryConfig, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
expectedExportIdsDirect.forEach(targetId => { expectedExportIdsDirect.forEach(targetId => {

View file

@ -1,20 +1,19 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { import { it } from 'mocha';
mockProject, import { mockProject, restoreMockedProjects } from '../../../test-helpers/mock-project-helpers.js';
restoreMockedProjects, import { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js';
} = require('../../../test-helpers/mock-project-helpers.js'); import { QueryService } from '../../../src/program/core/QueryService.js';
const { setupAnalyzerTest } = require('../../../test-helpers/setup-analyzer-test.js'); import { providence } from '../../../src/program/providence.js';
const { QueryService } = require('../../../src/program/core/QueryService.js'); import { DummyAnalyzer } from '../../../test-helpers/templates/DummyAnalyzer.js';
const { providence } = require('../../../src/program/providence.js');
const { DummyAnalyzer } = require('../../../test-helpers/templates/DummyAnalyzer.js');
/** /**
* @typedef {import('../../../src/program/types/core').ProvidenceConfig} ProvidenceConfig * @typedef {import('../../../types/index.js').ProvidenceConfig} ProvidenceConfig
*/ */
describe('Analyzer', () => { setupAnalyzerTest();
describe('Analyzer', async () => {
const dummyAnalyzer = new DummyAnalyzer(); const dummyAnalyzer = new DummyAnalyzer();
const queryResults = setupAnalyzerTest();
describe('Public api', () => { describe('Public api', () => {
it('has a "name" string', async () => { it('has a "name" string', async () => {
@ -41,17 +40,17 @@ describe('Analyzer', () => {
restoreMockedProjects(); restoreMockedProjects();
}); });
const myQueryConfigObject = QueryService.getQueryConfigFromAnalyzer(DummyAnalyzer); const myQueryConfigObject = await QueryService.getQueryConfigFromAnalyzer(DummyAnalyzer);
/** @type {Partial<ProvidenceConfig>} */ /** @type {Partial<ProvidenceConfig>} */
const _providenceCfg = { const _providenceCfg = {
targetProjectPaths: ['/fictional/project'], targetProjectPaths: ['/fictional/project'],
}; };
describe('Prepare phase', () => { describe('Prepare phase', () => {
it('looks for a cached result', async () => { it.skip('looks for a cached result', async () => {
// Our configuration object // Our configuration object
mockProject([`const validJs = true;`, `let invalidJs = false;`]); mockProject([`const validJs = true;`, `let invalidJs = false;`]);
await providence(myQueryConfigObject, _providenceCfg); // const queryResults = await providence(myQueryConfigObject, _providenceCfg);
}); });
it('exposes a ".targetMeta" object', async () => {}); it('exposes a ".targetMeta" object', async () => {});
@ -76,8 +75,8 @@ describe('Analyzer', () => {
}); });
describe('Finalize phase', () => { describe('Finalize phase', () => {
it('returns an AnalyzerQueryResult', async () => { it.skip('returns an AnalyzerQueryResult', async () => {
await providence(myQueryConfigObject, _providenceCfg); const queryResults = await providence(myQueryConfigObject, _providenceCfg);
const queryResult = queryResults[0]; const queryResult = queryResults[0];
const { queryOutput, meta } = queryResult; const { queryOutput, meta } = queryResult;

View file

@ -1,15 +1,18 @@
const { expect } = require('chai'); import { expect } from 'chai';
const pathLib = require('path'); import { it } from 'mocha';
const { InputDataService } = require('../../../src/program/core/InputDataService.js'); import pathLib from 'path';
const { memoizeConfig } = require('../../../src/program/utils/memoize.js'); import { InputDataService } from '../../../src/program/core/InputDataService.js';
const { import { memoizeConfig } from '../../../src/program/utils/memoize.js';
import { getCurrentDir } from '../../../src/program/utils/get-current-dir.js';
import {
restoreMockedProjects, restoreMockedProjects,
mockProject, mockProject,
mock, mock,
} = require('../../../test-helpers/mock-project-helpers.js'); } from '../../../test-helpers/mock-project-helpers.js';
// import { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js';
/** /**
* @typedef {import('../../../src/program/types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
*/ */
describe('InputDataService', () => { describe('InputDataService', () => {
@ -52,7 +55,7 @@ describe('InputDataService', () => {
it('"createDataObject"', async () => { it('"createDataObject"', async () => {
/** @type {* & PathFromSystemRoot} */ /** @type {* & PathFromSystemRoot} */
const projectPath = pathLib.resolve( const projectPath = pathLib.resolve(
__dirname, getCurrentDir(import.meta.url),
'../../../test-helpers/project-mocks/importing-target-project', '../../../test-helpers/project-mocks/importing-target-project',
); );

View file

@ -1,11 +1,12 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { QueryService } = require('../../../src/program/core/QueryService.js'); import { it } from 'mocha';
const { DummyAnalyzer } = require('../../../test-helpers/templates/DummyAnalyzer.js'); import { QueryService } from '../../../src/program/core/QueryService.js';
const FindImportsAnalyzer = require('../../../src/program/analyzers/find-imports.js'); import { DummyAnalyzer } from '../../../test-helpers/templates/DummyAnalyzer.js';
import FindImportsAnalyzer from '../../../src/program/analyzers/find-imports.js';
/** /**
* @typedef {import('../../../src/program/types/core').Analyzer} Analyzer * @typedef {import('../../../types/index.js').Analyzer} Analyzer
* @typedef {import('../../../src/program/types/core').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
*/ */
describe('QueryService', () => { describe('QueryService', () => {
@ -144,6 +145,7 @@ describe('QueryService', () => {
it('throws when no string provided', async () => { it('throws when no string provided', async () => {
expect(() => { expect(() => {
// @ts-ignore
QueryService.getQueryConfigFromFeatureString(); QueryService.getQueryConfigFromFeatureString();
}).to.throw('[QueryService.getQueryConfigFromFeatureString]: provide a string'); }).to.throw('[QueryService.getQueryConfigFromFeatureString]: provide a string');
}); });
@ -152,7 +154,10 @@ describe('QueryService', () => {
describe('"getQueryConfigFromAnalyzer"', () => { describe('"getQueryConfigFromAnalyzer"', () => {
const myAnalyzerCfg = { targetProjectPath: /** @type {PathFromSystemRoot} */ ('/my/path') }; const myAnalyzerCfg = { targetProjectPath: /** @type {PathFromSystemRoot} */ ('/my/path') };
it('accepts a constructor as first argument', async () => { it('accepts a constructor as first argument', async () => {
const result = QueryService.getQueryConfigFromAnalyzer('find-imports', myAnalyzerCfg); const result = await QueryService.getQueryConfigFromAnalyzer(
'find-imports',
myAnalyzerCfg,
);
expect(result).to.eql({ expect(result).to.eql({
type: 'ast-analyzer', type: 'ast-analyzer',
analyzerName: 'find-imports', analyzerName: 'find-imports',
@ -162,13 +167,13 @@ describe('QueryService', () => {
}); });
it('accepts a string as first argument', async () => { it('accepts a string as first argument', async () => {
const result = QueryService.getQueryConfigFromAnalyzer( const result = await QueryService.getQueryConfigFromAnalyzer(
/** @type {* & Analyzer} */ (DummyAnalyzer), /** @type {* & Analyzer} */ (DummyAnalyzer),
myAnalyzerCfg, myAnalyzerCfg,
); );
expect(result).to.eql({ expect(result).to.eql({
type: 'ast-analyzer', type: 'ast-analyzer',
analyzerName: 'dummy-analyzer', analyzerName: 'find-dummy-analyzer',
analyzerConfig: myAnalyzerCfg, analyzerConfig: myAnalyzerCfg,
analyzer: DummyAnalyzer, analyzer: DummyAnalyzer,
}); });
@ -176,22 +181,22 @@ describe('QueryService', () => {
}); });
}); });
describe('QueryResults', () => { // describe('QueryResults', () => {
describe.skip('"grepSearch"', () => { // describe.skip('"grepSearch"', () => {
it('with FeatureConfig', async () => { // it('with FeatureConfig', async () => {
const featureCfg = QueryService.getQueryConfigFromFeatureString('tg-icon[size=xs]'); // const featureCfg = QueryService.getQueryConfigFromFeatureString('tg-icon[size=xs]');
const result = QueryService.grepSearch(featureCfg); // const result = QueryService.grepSearch(featureCfg);
expect(result).to.eql({ // expect(result).to.eql({
type: 'ast-analyzer', // type: 'ast-analyzer',
analyzerName: 'find-imports', // analyzerName: 'find-imports',
analyzerConfig: { x: 'y' }, // analyzerConfig: { x: 'y' },
analyzer: FindImportsAnalyzer, // analyzer: FindImportsAnalyzer,
}); // });
}); // });
}); // });
it('"astSearch"', async () => {}); // it('"astSearch"', async () => {});
}); // });
describe('Ast retrieval', () => { describe('Ast retrieval', () => {
it('"addAstToProjectsData"', async () => {}); it('"addAstToProjectsData"', async () => {});

View file

@ -1,8 +1,18 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { mock } = require('../../../test-helpers/mock-project-helpers.js'); import { it } from 'mocha';
const { getSourceCodeFragmentOfDeclaration } = require('../../../src/program/utils/index.js'); import { mock } from '../../../test-helpers/mock-project-helpers.js';
import { getSourceCodeFragmentOfDeclaration } from '../../../src/program/utils/index.js';
import { memoizeConfig } from '../../../src/program/utils/memoize.js';
describe('getSourceCodeFragmentOfDeclaration', () => { describe('getSourceCodeFragmentOfDeclaration', () => {
const initialMemoizeSsCacheDisabled = memoizeConfig.isCacheDisabled;
before(() => {
memoizeConfig.isCacheDisabled = true;
});
after(() => {
memoizeConfig.isCacheDisabled = initialMemoizeSsCacheDisabled;
});
describe('Named specifiers', () => { describe('Named specifiers', () => {
it('finds source code for directly declared specifiers', async () => { it('finds source code for directly declared specifiers', async () => {
const fakeFs = { const fakeFs = {

View file

@ -1,5 +1,6 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { memoize, memoizeConfig } = require('../../../src/program/utils/memoize.js'); import { it } from 'mocha';
import { memoize, memoizeConfig } from '../../../src/program/utils/memoize.js';
const cacheDisabledInitialValue = memoizeConfig.isCacheDisabled; const cacheDisabledInitialValue = memoizeConfig.isCacheDisabled;

View file

@ -1,11 +1,12 @@
const { expect } = require('chai'); import { expect } from 'chai';
const { import { it } from 'mocha';
import {
mockProject, mockProject,
restoreMockedProjects, restoreMockedProjects,
mockTargetAndReferenceProject, mockTargetAndReferenceProject,
} = require('../../../test-helpers/mock-project-helpers.js'); } from '../../../test-helpers/mock-project-helpers.js';
const { resolveImportPath } = require('../../../src/program/utils/resolve-import-path.js'); import { resolveImportPath } from '../../../src/program/utils/resolve-import-path.js';
const { memoizeConfig } = require('../../../src/program/utils/memoize.js'); import { memoizeConfig } from '../../../src/program/utils/memoize.js';
describe('resolveImportPath', () => { describe('resolveImportPath', () => {
beforeEach(() => { beforeEach(() => {

View file

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist-types",
"rootDir": "."
},
"include": ["src", "dashboard", "types"],
"exclude": ["dist-types"]
}

View file

@ -4,7 +4,7 @@ import {
RootFile, RootFile,
AnalyzerQueryResult, AnalyzerQueryResult,
FindAnalyzerOutputFile, FindAnalyzerOutputFile,
} from '../core'; } from '../core/index.js';
export interface FindClassesAnalyzerResult extends AnalyzerQueryResult { export interface FindClassesAnalyzerResult extends AnalyzerQueryResult {
queryOutput: FindClassesAnalyzerOutputFile[]; queryOutput: FindClassesAnalyzerOutputFile[];

View file

@ -4,7 +4,7 @@ import {
RootFile, RootFile,
AnalyzerQueryResult, AnalyzerQueryResult,
FindAnalyzerOutputFile, FindAnalyzerOutputFile,
} from '../core'; } from '../core/index.js';
export interface FindCustomelementsAnalyzerResult extends AnalyzerQueryResult { export interface FindCustomelementsAnalyzerResult extends AnalyzerQueryResult {
queryOutput: FindCustomelementsAnalyzerOutputFile[]; queryOutput: FindCustomelementsAnalyzerOutputFile[];

View file

@ -6,7 +6,7 @@ import {
RootFile, RootFile,
AnalyzerQueryResult, AnalyzerQueryResult,
FindAnalyzerOutputFile, FindAnalyzerOutputFile,
} from '../core'; } from '../core/index.js';
export interface FindExportsAnalyzerResult extends AnalyzerQueryResult { export interface FindExportsAnalyzerResult extends AnalyzerQueryResult {
queryOutput: FindExportsAnalyzerOutputFile[]; queryOutput: FindExportsAnalyzerOutputFile[];

View file

@ -4,7 +4,7 @@ import {
PathRelativeFromProjectRoot, PathRelativeFromProjectRoot,
AnalyzerQueryResult, AnalyzerQueryResult,
FindAnalyzerOutputFile, FindAnalyzerOutputFile,
} from '../core'; } from '../core/index.js';
export interface FindImportsAnalyzerResult extends AnalyzerQueryResult { export interface FindImportsAnalyzerResult extends AnalyzerQueryResult {
queryOutput: FindImportsAnalyzerOutputFile[]; queryOutput: FindImportsAnalyzerOutputFile[];
@ -41,6 +41,8 @@ export interface FindImportsAnalyzerEntry {
* - file `import { x } from '../';` gives `"../index.js"` * - file `import { x } from '../';` gives `"../index.js"`
*/ */
normalizedSource: SpecifierSource; normalizedSource: SpecifierSource;
assertionType: string;
} }
/** /**

View file

@ -0,0 +1,6 @@
export * from './find-classes.js';
export * from './find-customelements.js';
export * from './find-exports.js';
export * from './find-imports.js';
export * from './match-imports.js';
export * from './match-subclasses.js';

View file

@ -1,5 +1,9 @@
import { ImportOrExportId, PathRelativeFromProjectRoot, ProjectName } from '../core/core'; import { ImportOrExportId, PathRelativeFromProjectRoot, ProjectName } from '../core/core.js';
import { AnalyzerQueryResult, MatchedExportSpecifier, MatchAnalyzerConfig } from '../core/Analyzer'; import {
AnalyzerQueryResult,
MatchedExportSpecifier,
MatchAnalyzerConfig,
} from '../core/Analyzer.js';
export interface MatchImportsAnalyzerResult extends AnalyzerQueryResult { export interface MatchImportsAnalyzerResult extends AnalyzerQueryResult {
queryOutput: MatchImportsAnalyzerOutputEntry[]; queryOutput: MatchImportsAnalyzerOutputEntry[];

View file

@ -4,7 +4,7 @@ import {
PathRelativeFromProjectRoot, PathRelativeFromProjectRoot,
AnalyzerQueryResult, AnalyzerQueryResult,
MatchedExportSpecifier, MatchedExportSpecifier,
} from '../core'; } from '../core/index.js';
export interface MatchSubclassesAnalyzerResult extends AnalyzerQueryResult { export interface MatchSubclassesAnalyzerResult extends AnalyzerQueryResult {
queryOutput: MatchSubclassesAnalyzerOutputEntry[]; queryOutput: MatchSubclassesAnalyzerOutputEntry[];

View file

@ -9,12 +9,12 @@ import {
GatherFilesConfig, GatherFilesConfig,
SpecifierName, SpecifierName,
QueryOutput, QueryOutput,
} from './index'; } from './index.js';
/** /**
* Name of the analyzer, like 'find-imports' or 'match-sublcasses' * Name of the analyzer, like 'find-imports' or 'match-sublcasses'
*/ */
export type AnalyzerName = `${'find' | 'match'}-${string}`; export type AnalyzerName = `${'find' | 'match'}-${string}` | '';
// TODO: make sure that data structures of JSON output (generated in ReportService) // TODO: make sure that data structures of JSON output (generated in ReportService)
// and data structure generated in Analyzer.prototype._finalize match exactly (move logic from ReportSerivce to _finalize) // and data structure generated in Analyzer.prototype._finalize match exactly (move logic from ReportSerivce to _finalize)

View file

@ -1,6 +1,6 @@
import { AnalyzerName, Feature, AnalyzerConfig, PathRelativeFromProjectRoot } from './index'; import { AnalyzerName, Feature, AnalyzerConfig, PathRelativeFromProjectRoot } from './index.js';
import { Analyzer } from '../../core/Analyzer'; import { Analyzer } from '../../src/program/core/Analyzer.js';
export { Analyzer } from '../../core/Analyzer'; export { Analyzer } from '../../src/program/core/Analyzer.js';
/** /**
* Type of the query. Currently only "ast-analyzer" supported * Type of the query. Currently only "ast-analyzer" supported

View file

@ -173,6 +173,7 @@ export type ProvidenceConfig = {
measurePerformance: boolean; measurePerformance: boolean;
writeLogFile: boolean; writeLogFile: boolean;
skipCheckMatchCompatibility: boolean; skipCheckMatchCompatibility: boolean;
fallbackToBabel: boolean;
}; };
/** /**

View file

@ -0,0 +1,3 @@
export * from './core.js';
export * from './Analyzer.js';
export * from './QueryService.js';

View file

@ -0,0 +1,3 @@
export * from './core/index.js';
export * from './analyzers/index.js';
export * from './misc.js';

View file

@ -0,0 +1,37 @@
export type PkgName = `@${string}/${string}` | string;
export type PkgVersion = `${number}.${number}.${number}`;
export type TargetDep = `${PkgName}#${PkgVersion}`;
export type TargetDepsObj = {
[key: TargetDep]: TargetDep[];
};
export type TargetOrRefCollectionsObj = {
[key: PkgName]: PkgName[];
};
export type ProvidenceCliConf = {
metaConfig: {
categoryConfig: {
/* This is the name found in package.json */
project: string;
majorVersion: number;
/* These conditions will be run on overy filePath */
categories: {
[category: string]: (localFilePath: string) => string[];
};
}[];
};
/*
* By predefening groups, we can do a query for programs/collections...
* Select via " providence analyze --search-target-collection 'exampleCollection' "
*/
searchTargetCollections: {
[targetCollection: string]: string[];
};
referenceCollections: {
/**
* 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' "
*/
[referenceCollection: string]: string[];
};
};

View file

@ -0,0 +1,53 @@
diff --git a/node_modules/swc-to-babel/lib/swc/index.js b/node_modules/swc-to-babel/lib/swc/index.js
index ab285b3..a58a61d 100644
--- a/node_modules/swc-to-babel/lib/swc/index.js
+++ b/node_modules/swc-to-babel/lib/swc/index.js
@@ -143,6 +143,15 @@ module.exports.ClassMethod = (path) => {
key,
});
+ if (node.kind === 'getter') {
+ node.kind = 'get';
+ }
+ if (node.kind === 'setter') {
+ node.kind = 'set';
+ }
+
+ node.static = node.isStatic;
+
delete path.node.isStatic;
delete path.node.accessibility;
delete path.node.isAbstract;
@@ -301,7 +310,7 @@ module.exports.TSIndexedAccessType = (path) => {
module.exports.ImportDeclaration = ({node}) => {
const {typeOnly} = node;
- node.assertions = node.asserts || [];
+ node.assertions = node.asserts?.properties || [];
node.importKind = typeOnly ? 'type' : 'value';
delete node.asserts;
@@ -340,9 +349,10 @@ module.exports.convertGetterSetter = ({node}) => {
};
module.exports.ExportDefaultDeclaration = ({node}) => {
- node.declaration = node.decl;
+ // node.declaration may have been already provided by convertExportDefaultExpression
+ node.declaration = node.declaration || node.decl;
node.exportKind = 'value';
- node.assertions = [];
+ node.assertions = node.asserts?.properties || [];
delete node.decl;
};
@@ -350,8 +360,8 @@ module.exports.ExportDefaultDeclaration = ({node}) => {
module.exports.ExportNamedDeclaration = ({node}) => {
const {typeOnly} = node;
- node.assertions = [];
- node.source = null;
+ node.assertions = node.asserts?.properties || [];
+ // node.source = null;
node.specifiers = node.specifiers || [];
node.exportKind = typeOnly ? 'type' : 'value';