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: Providence expects an analyzer name that tells it what type of analysis to run:
```bash ```bash
npx providence analyze <analyzer-name> npx providence-analytics analyze <analyzer-name>
``` ```
By default Providence ships these analyzers: By default Providence ships these analyzers:
@ -42,7 +42,7 @@ By default Providence ships these analyzers:
Let's say we run `find-imports`: Let's say we run `find-imports`:
```bash ```bash
npx providence analyze find-imports npx providence-analytics analyze find-imports
``` ```
Now it retrieves all relevant data about es module 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): We can specify it like this (we override the default current working directory):
```bash ```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. 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). Here we match the exports of the reference project (-r) against the imports of the target project (-t).
```bash ```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 ## Utils
@ -91,5 +91,5 @@ For a better understanding, check out the utils folders (tests and code).
For more options, see: For more options, see:
```bash ```bash
npx providence --help npx providence-analytics --help
``` ```

31
package-lock.json generated
View file

@ -5306,6 +5306,7 @@
"version": "15.3.0", "version": "15.3.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz",
"integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==", "integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rollup/pluginutils": "^5.0.1", "@rollup/pluginutils": "^5.0.1",
@ -28331,9 +28332,9 @@
"version": "0.17.3", "version": "0.17.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-node-resolve": "^16.0.0",
"commander": "^2.20.3", "commander": "^2.20.3",
"oxc-parser": "^0.39.0", "oxc-parser": "^0.46.0",
"parse5": "^7.2.1", "parse5": "^7.2.1",
"semver": "^7.6.3" "semver": "^7.6.3"
}, },
@ -28598,6 +28599,30 @@
"win32" "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": { "packages-node/providence-analytics/node_modules/@types/mocha": {
"version": "10.0.10", "version": "10.0.10",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
@ -28823,7 +28848,7 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@lion/ui", "name": "@lion/ui",
"version": "0.9.0", "version": "0.9.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@bundled-es-modules/message-format": "^6.2.4", "@bundled-es-modules/message-format": "^6.2.4",

View file

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

View file

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

View file

@ -234,37 +234,10 @@ export default class FindClassesAnalyzer extends Analyzer {
/** @type {AnalyzerAst} */ /** @type {AnalyzerAst} */
static requiredAst = 'oxc'; static requiredAst = 'oxc';
/** static async analyzeFile(oxcAst, context) {
* Will find all public members (properties (incl. getter/setters)/functions) of a class and const projectPath = context.analyzerCfg.targetProjectPath;
* will make a distinction between private, public and protected methods const fullPath = path.resolve(projectPath, context.relativePath);
* @param {Partial<FindClassesConfig>} customConfig const transformedEntry = await findMembersPerAstEntry(oxcAst, fullPath, projectPath);
*/ return { result: transformedEntry };
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);
} }
} }

View file

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

View file

@ -1,6 +1,7 @@
/* eslint-disable no-shadow, no-param-reassign */ /* eslint-disable no-shadow, no-param-reassign */
import path from 'path'; 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 { getReferencedDeclaration } from '../utils/get-source-code-fragment-of-declaration.js';
import { normalizeSourcePaths } from './helpers/normalize-source-paths.js'; import { normalizeSourcePaths } from './helpers/normalize-source-paths.js';
import { trackDownIdentifier } from '../utils/track-down-identifier.js'; import { trackDownIdentifier } from '../utils/track-down-identifier.js';
@ -274,4 +275,10 @@ export default class FindExportsAnalyzer extends Analyzer {
return { result: transformedFile }; 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 * Intends to work for oxc, swc, and babel asts
* @param {SwcNode} s
*/ */
function getSpecifierValue(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 ( return (
// These are regular import values and must be checked first // These are regular import values and must be checked first
s.imported?.value || s.imported?.value ||
@ -164,57 +157,35 @@ export default class FindImportsSwcAnalyzer extends Analyzer {
static requiredAst = /** @type {AnalyzerAst} */ ('oxc'); static requiredAst = /** @type {AnalyzerAst} */ ('oxc');
/** /**
* Finds import specifiers and sources * @typedef FindImportsConfig
* @param {FindImportsConfig} customConfig * @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 = {}) { get config() {
/** return {
* @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 = {
targetProjectPath: null, targetProjectPath: null,
// post process file // post process file
keepInternalSources: false, keepInternalSources: false,
...customConfig, ...this._customConfig,
}; };
}
/** static async analyzeFile(oxcAst, context) {
* Prepare let transformedFile = findImportsPerAstFile(oxcAst);
*/ // Post processing based on configuration...
const cachedAnalyzerResult = await this._prepare(cfg); transformedFile = await normalizeSourcePaths(
if (cachedAnalyzerResult) { transformedFile,
return cachedAnalyzerResult; context.relativePath,
context.analyzerCfg.targetProjectPath,
);
if (!context.analyzerCfg.keepInternalSources) {
// @ts-expect-error
transformedFile = transformedFile.filter(entry => !isRelativeSourcePath(entry.source));
} }
/** return { result: transformedFile };
* 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);
} }
} }

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`); LogService.debug(`Analyzer "${this.name}": started _traverse method`);
let traverseEntryFn;
let finalTargetData; let finalTargetData;
if (!analyzeFileCfg.filePaths) {
if (typeof traverseEntryOrConfig === 'function') { finalTargetData = analyzeFileCfg.targetData;
traverseEntryFn = traverseEntryOrConfig;
finalTargetData = this.targetData;
} else { } else {
traverseEntryFn = traverseEntryOrConfig.traverseEntryFn; const { projectPath, projectName } = analyzeFileCfg;
if (!traverseEntryOrConfig.filePaths) { if (!projectPath) {
finalTargetData = this.targetData; LogService.error(`[Analyzer._traverse]: you must provide a projectPath`);
} 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,
},
]);
} }
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( const astDataProjects = await QueryService.addAstToProjectsData(
finalTargetData, 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 * Traverse
*/ */
const queryOutput = await this._traverse({ const queryOutput = await /** @type {typeof Analyzer} */ (this.constructor).analyzeProject({
// @ts-ignore // @ts-ignore
traverseEntryFn: this.constructor.analyzeFile, traverseEntryFn: this.constructor.analyzeFile,
filePaths: cfg.targetFilePaths,
projectPath: cfg.targetProjectPath, 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 * @typedef {import('../../../types/index.js').Feature} Feature
*/ */
/** @type {`.${string}`[]} */
const defaultExtensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs'];
/** /**
* @typedef {(rootPath:PathFromSystemRoot) => PackageJson|undefined} GetPackageJsonFn * @typedef {(rootPath:PathFromSystemRoot) => PackageJson|undefined} GetPackageJsonFn
* @type {GetPackageJsonFn} * @type {GetPackageJsonFn}
@ -179,8 +182,9 @@ const getNpmPackagePaths = memoize((/** @type {PathFromSystemRoot} */ rootPath)
}); });
/** /**
* @param {any|any[]} v * @template T
* @returns {any[]} * @param {T|T[]} v
* @returns {T[]}
*/ */
function ensureArray(v) { function ensureArray(v) {
return Array.isArray(v) ? v : [v]; return Array.isArray(v) ? v : [v];
@ -429,7 +433,7 @@ export class InputDataService {
static get defaultGatherFilesConfig() { static get defaultGatherFilesConfig() {
return { return {
allowlist: ['!node_modules/**', '!bower_components/**', '!**/*.conf.js', '!**/*.config.js'], allowlist: ['!node_modules/**', '!bower_components/**', '!**/*.conf.js', '!**/*.config.js'],
extensions: ['.js'], extensions: defaultExtensions,
depth: Infinity, depth: Infinity,
}; };
} }
@ -440,7 +444,7 @@ export class InputDataService {
* @param {string[]} extensions * @param {string[]} extensions
* @returns {string} * @returns {string}
*/ */
static _getDefaultGlobDepthPattern(depth = Infinity, extensions = ['.js']) { static _getDefaultGlobDepthPattern(depth = Infinity, extensions = defaultExtensions) {
// `.{${cfg.extensions.map(e => e.slice(1)).join(',')},}`; // `.{${cfg.extensions.map(e => e.slice(1)).join(',')},}`;
const extensionsGlobPart = `.{${extensions.map(extension => extension.slice(1)).join(',')},}`; const extensionsGlobPart = `.{${extensions.map(extension => extension.slice(1)).join(',')},}`;
if (depth === Infinity) { if (depth === Infinity) {
@ -472,6 +476,15 @@ export class InputDataService {
* @returns {Promise<PathFromSystemRoot[]>} result list of file paths * @returns {Promise<PathFromSystemRoot[]>} result list of file paths
*/ */
static async gatherFilesFromDir(startPath, customConfig = {}) { 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 = { const cfg = {
...this.defaultGatherFilesConfig, ...this.defaultGatherFilesConfig,
...customConfig, ...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') { if (cfg.allowlistMode === 'export-map') {
const pkgJson = getPackageJson(startPath); const pkgJson = getPackageJson(startPath);
if (!pkgJson?.exports) { if (!pkgJson?.exports) {

View file

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