feat(providence): update version of oxc; cleanup; include .ts(x) and jsx by default

This commit is contained in:
Thijs Louisse 2025-01-14 17:30:06 +01:00 committed by Thijs Louisse
parent 35e66052b6
commit 71992cc0fb
12 changed files with 133 additions and 170 deletions

View file

@ -0,0 +1,5 @@
---
'providence-analytics': patch
---
update version of oxc; cleanup; include .ts(x) and jsx by default

View file

@ -28,7 +28,7 @@ It does this via the [oxc parser](https://oxc.rs/docs/guide/usage/parser.html),
Providence expects an analyzer name that tells it what type of analysis to run:
```bash
npx providence analyze <analyzer-name>
npx providence-analytics analyze <analyzer-name>
```
By default Providence ships these analyzers:
@ -42,7 +42,7 @@ By default Providence ships these analyzers:
Let's say we run `find-imports`:
```bash
npx providence analyze find-imports
npx providence-analytics analyze find-imports
```
Now it retrieves all relevant data about es module imports.
@ -68,14 +68,14 @@ For a "find" analyzer, there is one project involved (the target project).
We can specify it like this (we override the default current working directory):
```bash
npx providence analyze find-imports -t /importing/project
npx providence-analytics analyze find-imports -t /importing/project
```
For a "match" analyzer, there is also a reference project.
Here we match the exports of the reference project (-r) against the imports of the target project (-t).
```bash
npx providence analyze match-imports -t /importing/project -r /exporting/project
npx providence-analytics analyze match-imports -t /importing/project -r /exporting/project
```
## Utils
@ -91,5 +91,5 @@ For a better understanding, check out the utils folders (tests and code).
For more options, see:
```bash
npx providence --help
npx providence-analytics --help
```

31
package-lock.json generated
View file

@ -5306,6 +5306,7 @@
"version": "15.3.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz",
"integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
@ -28331,9 +28332,9 @@
"version": "0.17.3",
"license": "MIT",
"dependencies": {
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"commander": "^2.20.3",
"oxc-parser": "^0.39.0",
"oxc-parser": "^0.46.0",
"parse5": "^7.2.1",
"semver": "^7.6.3"
},
@ -28598,6 +28599,30 @@
"win32"
]
},
"packages-node/providence-analytics/node_modules/@rollup/plugin-node-resolve": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.0.tgz",
"integrity": "sha512-0FPvAeVUT/zdWoO0jnb/V5BlBsUSNfkIOtFHzMO4H9MOklrmQFY6FduVHKucNb/aTFxvnGhj4MNj/T1oNdDfNg==",
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",
"deepmerge": "^4.2.2",
"is-module": "^1.0.0",
"resolve": "^1.22.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.78.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"packages-node/providence-analytics/node_modules/@types/mocha": {
"version": "10.0.10",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
@ -28823,7 +28848,7 @@
},
"packages/ui": {
"name": "@lion/ui",
"version": "0.9.0",
"version": "0.9.1",
"license": "MIT",
"dependencies": {
"@bundled-es-modules/message-format": "^6.2.4",

View file

@ -37,9 +37,9 @@
"test:node:unit": "mocha './{test-node,src}/**/*.test.js'"
},
"dependencies": {
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"commander": "^2.20.3",
"oxc-parser": "^0.39.0",
"oxc-parser": "^0.46.0",
"parse5": "^7.2.1",
"semver": "^7.6.3"
},

View file

@ -1,6 +1,5 @@
import path from 'path';
import commander from 'commander';
import path from 'path';
import { InputDataService } from '../program/core/InputDataService.js';
import { getCurrentDir } from '../program/utils/get-current-dir.js';

View file

@ -234,37 +234,10 @@ export default class FindClassesAnalyzer extends Analyzer {
/** @type {AnalyzerAst} */
static requiredAst = 'oxc';
/**
* Will find all public members (properties (incl. getter/setters)/functions) of a class and
* will make a distinction between private, public and protected methods
* @param {Partial<FindClassesConfig>} customConfig
*/
async execute(customConfig) {
const cfg = customConfig;
/**
* Prepare
*/
const analyzerResult = await this._prepare(cfg);
if (analyzerResult) {
return analyzerResult;
}
/**
* Traverse
*/
/** @type {FindClassesAnalyzerOutput} */
const queryOutput = await this._traverse(async (ast, { relativePath }) => {
const projectPath = cfg.targetProjectPath;
const fullPath = path.resolve(projectPath, relativePath);
const transformedEntry = await findMembersPerAstEntry(ast, fullPath, projectPath);
return { result: transformedEntry };
});
// _flattenedFormsPostProcessor();
/**
* Finalize
*/
return this._finalize(queryOutput, cfg);
static async analyzeFile(oxcAst, context) {
const projectPath = context.analyzerCfg.targetProjectPath;
const fullPath = path.resolve(projectPath, context.relativePath);
const transformedEntry = await findMembersPerAstEntry(oxcAst, fullPath, projectPath);
return { result: transformedEntry };
}
}

View file

@ -7,7 +7,7 @@ import { trackDownIdentifierFromScope } from '../utils/track-down-identifier.js'
import { Analyzer } from '../core/Analyzer.js';
/**
* @typedef {import('../../../types/index.js').FindCustomelementsConfig} FindCustomelementsConfig
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
* @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
* @typedef {import('@babel/types').File} File
*/
@ -100,38 +100,21 @@ export default class FindCustomelementsAnalyzer extends Analyzer {
/** @type {AnalyzerAst} */
static requiredAst = 'oxc';
/**
* Finds export specifiers and sources
* @param {FindCustomelementsConfig} customConfig
*/
async execute(customConfig = {}) {
const cfg = {
get config() {
return {
targetProjectPath: null,
...customConfig,
...this._customConfig,
};
}
/**
* Prepare
*/
const cachedAnalyzerResult = await this._prepare(cfg);
if (cachedAnalyzerResult) {
return cachedAnalyzerResult;
}
/**
* Traverse
*/
const projectPath = cfg.targetProjectPath;
const queryOutput = await this._traverse(async (ast, context) => {
let transformedEntry = findCustomElementsPerAstFile(ast);
transformedEntry = await trackdownRoot(transformedEntry, context.relativePath, projectPath);
transformedEntry = cleanup(transformedEntry);
return { result: transformedEntry };
});
/**
* Finalize
*/
return this._finalize(queryOutput, cfg);
static async analyzeFile(oxcAst, context) {
let transformedEntry = findCustomElementsPerAstFile(oxcAst);
transformedEntry = await trackdownRoot(
transformedEntry,
context.relativePath,
context.projectData.project.path,
);
transformedEntry = cleanup(transformedEntry);
return { result: transformedEntry };
}
}

View file

@ -1,6 +1,7 @@
/* eslint-disable no-shadow, no-param-reassign */
import path from 'path';
// import { transformIntoIterableFindExportsOutput } from './helpers/transform-into-iterable-find-exports-output.js';
import { getReferencedDeclaration } from '../utils/get-source-code-fragment-of-declaration.js';
import { normalizeSourcePaths } from './helpers/normalize-source-paths.js';
import { trackDownIdentifier } from '../utils/track-down-identifier.js';
@ -274,4 +275,10 @@ export default class FindExportsAnalyzer extends Analyzer {
return { result: transformedFile };
}
static async analyzeProject(...args) {
const totalResult = await super.analyzeProject(...args);
// return transformIntoIterableFindExportsOutput({ queryOutput: totalResult });
return totalResult;
}
}

View file

@ -23,16 +23,9 @@ import { Analyzer } from '../core/Analyzer.js';
/**
* Intends to work for oxc, swc, and babel asts
* @param {SwcNode} s
*/
function getSpecifierValue(s) {
// for (const exportedorImportedName of [...exportedNames, ...importedNames]) {
// for (const valueName of valueNames) {
// const result = s[exportedorImportedName][valueName];
// if (result) return result;
// }
// }
// return undefined;
return (
// These are regular import values and must be checked first
s.imported?.value ||
@ -164,57 +157,35 @@ export default class FindImportsSwcAnalyzer extends Analyzer {
static requiredAst = /** @type {AnalyzerAst} */ ('oxc');
/**
* Finds import specifiers and sources
* @param {FindImportsConfig} customConfig
* @typedef FindImportsConfig
* @property {boolean} [keepInternalSources=false] by default, relative paths like '../x.js' are
* filtered out. This option keeps them.
* means that 'external-dep/file' will be resolved to 'external-dep/file.js' will both be stored
* as the latter
*/
async execute(customConfig = {}) {
/**
* @typedef FindImportsConfig
* @property {boolean} [keepInternalSources=false] by default, relative paths like '../x.js' are
* filtered out. This option keeps them.
* means that 'external-dep/file' will be resolved to 'external-dep/file.js' will both be stored
* as the latter
*/
const cfg = {
get config() {
return {
targetProjectPath: null,
// post process file
keepInternalSources: false,
...customConfig,
...this._customConfig,
};
}
/**
* Prepare
*/
const cachedAnalyzerResult = await this._prepare(cfg);
if (cachedAnalyzerResult) {
return cachedAnalyzerResult;
static async analyzeFile(oxcAst, context) {
let transformedFile = findImportsPerAstFile(oxcAst);
// Post processing based on configuration...
transformedFile = await normalizeSourcePaths(
transformedFile,
context.relativePath,
context.analyzerCfg.targetProjectPath,
);
if (!context.analyzerCfg.keepInternalSources) {
// @ts-expect-error
transformedFile = transformedFile.filter(entry => !isRelativeSourcePath(entry.source));
}
/**
* Traverse
*/
const queryOutput = await this._traverse(async (oxcAst, context) => {
// @ts-expect-error
let transformedFile = findImportsPerAstFile(oxcAst);
// Post processing based on configuration...
transformedFile = await normalizeSourcePaths(
transformedFile,
context.relativePath,
// @ts-expect-error
cfg.targetProjectPath,
);
if (!cfg.keepInternalSources) {
// @ts-expect-error
transformedFile = transformedFile.filter(entry => !isRelativeSourcePath(entry.source));
}
return { result: transformedFile };
});
/**
* Finalize
*/
return this._finalize(queryOutput, cfg);
return { result: transformedFile };
}
}

View file

@ -348,36 +348,28 @@ export class Analyzer {
}
/**
* @param {FileAstTraverseFn|{traverseEntryFn: FileAstTraverseFn; filePaths:string[]; projectPath: string}} traverseEntryOrConfig
* @param {FileAstTraverseFn|{traverseEntryFn: FileAstTraverseFn; filePaths:string[]; projectPath: string; targetData: ProjectInputDataWithMeta}} analyzeFileCfg
*/
async _traverse(traverseEntryOrConfig) {
static async analyzeProject(analyzeFileCfg) {
LogService.debug(`Analyzer "${this.name}": started _traverse method`);
let traverseEntryFn;
let finalTargetData;
if (typeof traverseEntryOrConfig === 'function') {
traverseEntryFn = traverseEntryOrConfig;
finalTargetData = this.targetData;
if (!analyzeFileCfg.filePaths) {
finalTargetData = analyzeFileCfg.targetData;
} else {
traverseEntryFn = traverseEntryOrConfig.traverseEntryFn;
if (!traverseEntryOrConfig.filePaths) {
finalTargetData = this.targetData;
} else {
const { projectPath, projectName } = traverseEntryOrConfig;
if (!projectPath) {
LogService.error(`[Analyzer._traverse]: you must provide a projectPath`);
}
finalTargetData = await InputDataService.createDataObject([
{
project: {
name: projectName || '[n/a]',
path: projectPath,
},
entries: traverseEntryOrConfig.filePaths,
},
]);
const { projectPath, projectName } = analyzeFileCfg;
if (!projectPath) {
LogService.error(`[Analyzer._traverse]: you must provide a projectPath`);
}
finalTargetData = await InputDataService.createDataObject([
{
project: {
name: projectName || '[n/a]',
path: projectPath,
},
entries: analyzeFileCfg.filePaths,
},
]);
}
/**
@ -385,9 +377,13 @@ export class Analyzer {
*/
const astDataProjects = await QueryService.addAstToProjectsData(
finalTargetData,
this.constructor.requiredAst,
this.requiredAst,
);
return analyzePerAstFile(
astDataProjects[0],
analyzeFileCfg.traverseEntryFn,
analyzeFileCfg.config,
);
return analyzePerAstFile(astDataProjects[0], traverseEntryFn, this.config);
}
/**
@ -409,11 +405,13 @@ export class Analyzer {
/**
* Traverse
*/
const queryOutput = await this._traverse({
const queryOutput = await /** @type {typeof Analyzer} */ (this.constructor).analyzeProject({
// @ts-ignore
traverseEntryFn: this.constructor.analyzeFile,
filePaths: cfg.targetFilePaths,
projectPath: cfg.targetProjectPath,
filePaths: cfg.targetFilePaths,
targetData: this.targetData,
config: this.config,
});
/**

View file

@ -34,6 +34,9 @@ import { AstService } from './AstService.js';
* @typedef {import('../../../types/index.js').Feature} Feature
*/
/** @type {`.${string}`[]} */
const defaultExtensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs'];
/**
* @typedef {(rootPath:PathFromSystemRoot) => PackageJson|undefined} GetPackageJsonFn
* @type {GetPackageJsonFn}
@ -179,8 +182,9 @@ const getNpmPackagePaths = memoize((/** @type {PathFromSystemRoot} */ rootPath)
});
/**
* @param {any|any[]} v
* @returns {any[]}
* @template T
* @param {T|T[]} v
* @returns {T[]}
*/
function ensureArray(v) {
return Array.isArray(v) ? v : [v];
@ -429,7 +433,7 @@ export class InputDataService {
static get defaultGatherFilesConfig() {
return {
allowlist: ['!node_modules/**', '!bower_components/**', '!**/*.conf.js', '!**/*.config.js'],
extensions: ['.js'],
extensions: defaultExtensions,
depth: Infinity,
};
}
@ -440,7 +444,7 @@ export class InputDataService {
* @param {string[]} extensions
* @returns {string}
*/
static _getDefaultGlobDepthPattern(depth = Infinity, extensions = ['.js']) {
static _getDefaultGlobDepthPattern(depth = Infinity, extensions = defaultExtensions) {
// `.{${cfg.extensions.map(e => e.slice(1)).join(',')},}`;
const extensionsGlobPart = `.{${extensions.map(extension => extension.slice(1)).join(',')},}`;
if (depth === Infinity) {
@ -472,6 +476,15 @@ export class InputDataService {
* @returns {Promise<PathFromSystemRoot[]>} result list of file paths
*/
static async gatherFilesFromDir(startPath, customConfig = {}) {
const allowlistModes = ['npm', 'git', 'all', 'export-map'];
if (customConfig.allowlistMode && !allowlistModes.includes(customConfig.allowlistMode)) {
throw new Error(
`[gatherFilesConfig] Please provide a valid allowListMode like "${allowlistModes.join(
'|',
)}". Found: "${customConfig.allowlistMode}"`,
);
}
const cfg = {
...this.defaultGatherFilesConfig,
...customConfig,
@ -483,15 +496,6 @@ export class InputDataService {
];
}
const allowlistModes = ['npm', 'git', 'all', 'export-map'];
if (customConfig.allowlistMode && !allowlistModes.includes(customConfig.allowlistMode)) {
throw new Error(
`[gatherFilesConfig] Please provide a valid allowListMode like "${allowlistModes.join(
'|',
)}". Found: "${customConfig.allowlistMode}"`,
);
}
if (cfg.allowlistMode === 'export-map') {
const pkgJson = getPackageJson(startPath);
if (!pkgJson?.exports) {

View file

@ -424,9 +424,7 @@ describe('Memoize', () => {
{ fn: spy2Memoized, count: 4 },
]);
console.debug('spy3Memoized');
spy3Memoized();
console.debug(memoize.cacheStrategyItems);
expect(memoize.cacheStrategyItems).to.deep.equal([
{ fn: spy2Memoized, count: 4 },
{ fn: spy3Memoized, count: 1 }, // we start over