From fd2e799ad52e2419d96b46990f648d6d29ae2cb0 Mon Sep 17 00:00:00 2001 From: Thijs Louisse Date: Thu, 6 Feb 2025 17:26:58 +0100 Subject: [PATCH] feat(providence): allow to run with swc; make all analyzers compatible with swc --- .changeset/smart-hairs-sneeze.md | 5 + package-lock.json | 35 +- .../providence-analytics/package.json | 6 +- .../src/program/analyzers/find-classes.js | 86 +- .../program/analyzers/find-customelements.js | 92 +- .../src/program/analyzers/find-imports.js | 10 +- .../src/program/analyzers/match-imports.js | 2 + .../src/program/core/Analyzer.js | 8 +- .../src/program/providence.js | 1 + .../src/program/utils/ast-normalizations.js | 110 ++ ...get-source-code-fragment-of-declaration.js | 3 +- .../src/program/utils/oxc-traverse.js | 62 +- .../program/utils/track-down-identifier.js | 3 +- .../find-classes.json | 16 +- .../program/analyzers/e2e/.gitignore | 1 + .../analyzers/e2e/all-analyzers.e2e.js | 240 ++-- ...project_0.0.2-target-mock__-905964591.json | 220 ---- ...t-project_0.0.2-target-mock__61665553.json | 52 - ...xporting-ref-project_1.0.0__-42206859.json | 194 --- ...-project_0.0.2-target-mock__349742630.json | 204 ---- ...xporting-ref-project_1.0.0__142861209.json | 94 -- ...porting-ref-project_1.0.0__1789378150.json | 162 --- ...xporting-ref-project_1.0.0__142861209.json | 94 -- ...porting-ref-project_1.0.0__1982316146.json | 67 -- .../program/analyzers/find-classes.test.js | 405 ++++--- .../analyzers/find-customelements.test.js | 166 +-- .../program/analyzers/find-exports.test.js | 604 +++++----- .../program/analyzers/find-imports.test.js | 530 +++++---- .../program/analyzers/match-imports.test.js | 811 ++++++------- .../program/analyzers/match-paths.test.js | 1053 +++++++++-------- .../analyzers/match-subclasses.test.js | 547 ++++----- .../program/utils/oxc-traverse.test.js | 13 +- .../providence-analytics/types/core/core.d.ts | 1 + 33 files changed, 2577 insertions(+), 3320 deletions(-) create mode 100644 .changeset/smart-hairs-sneeze.md create mode 100644 packages-node/providence-analytics/src/program/utils/ast-normalizations.js create mode 100644 packages-node/providence-analytics/test-node/program/analyzers/e2e/.gitignore delete mode 100644 packages-node/providence-analytics/test-node/program/analyzers/e2e/find-classes_-_importing-target-project_0.0.2-target-mock__-905964591.json delete mode 100644 packages-node/providence-analytics/test-node/program/analyzers/e2e/find-customelements_-_importing-target-project_0.0.2-target-mock__61665553.json delete mode 100644 packages-node/providence-analytics/test-node/program/analyzers/e2e/find-exports_-_exporting-ref-project_1.0.0__-42206859.json delete mode 100644 packages-node/providence-analytics/test-node/program/analyzers/e2e/find-imports_-_importing-target-project_0.0.2-target-mock__349742630.json delete mode 100644 packages-node/providence-analytics/test-node/program/analyzers/e2e/match-imports_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209.json delete mode 100644 packages-node/providence-analytics/test-node/program/analyzers/e2e/match-imports_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1789378150.json delete mode 100644 packages-node/providence-analytics/test-node/program/analyzers/e2e/match-paths_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209.json delete mode 100644 packages-node/providence-analytics/test-node/program/analyzers/e2e/match-subclasses_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1982316146.json diff --git a/.changeset/smart-hairs-sneeze.md b/.changeset/smart-hairs-sneeze.md new file mode 100644 index 0000000000..0b63942698 --- /dev/null +++ b/.changeset/smart-hairs-sneeze.md @@ -0,0 +1,5 @@ +--- +'providence-analytics': patch +--- + +allow to run with swc; make all analyzers compatible with swc diff --git a/package-lock.json b/package-lock.json index c7b834c811..d526a51f20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5903,6 +5903,16 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@swc/types": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", + "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@szmarczak/http-timer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", @@ -28246,14 +28256,14 @@ "license": "MIT" }, "packages-node/providence-analytics": { - "version": "0.18.1", + "version": "0.18.4", "license": "MIT", "dependencies": { "@rollup/plugin-node-resolve": "^16.0.0", "commander": "^2.20.3", "oxc-parser": "0.48.2", "parse5": "^7.2.1", - "semver": "^7.7.0" + "semver": "^7.7.1" }, "bin": { "providence": "src/cli/index.js" @@ -28262,7 +28272,7 @@ "@babel/parser": "^7.26.7", "@babel/plugin-syntax-import-assertions": "^7.26.0", "@babel/traverse": "^7.26.7", - "@swc/core": "^1.10.12", + "@swc/core": "^1.10.14", "@types/inquirer": "^9.0.7", "@types/mocha": "^10.0.10", "@web/dev-server": "^0.4.6", @@ -28277,7 +28287,7 @@ "peerDependencies": { "@babel/parser": "^7.25.8", "@babel/plugin-syntax-import-assertions": "^7.25.7", - "@swc/core": "^1.7.36" + "@swc/core": "^1.10.14" } }, "packages-node/providence-analytics/node_modules/@babel/code-frame": { @@ -28649,16 +28659,6 @@ "node": ">=10" } }, - "packages-node/providence-analytics/node_modules/@swc/types": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", - "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@swc/counter": "^0.1.3" - } - }, "packages-node/providence-analytics/node_modules/@types/mocha": { "version": "10.0.10", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", @@ -28765,9 +28765,10 @@ } }, "packages-node/providence-analytics/node_modules/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, diff --git a/packages-node/providence-analytics/package.json b/packages-node/providence-analytics/package.json index 52d8d66154..68517f518d 100644 --- a/packages-node/providence-analytics/package.json +++ b/packages-node/providence-analytics/package.json @@ -41,18 +41,18 @@ "commander": "^2.20.3", "oxc-parser": "0.48.2", "parse5": "^7.2.1", - "semver": "^7.7.0" + "semver": "^7.7.1" }, "peerDependencies": { "@babel/parser": "^7.25.8", "@babel/plugin-syntax-import-assertions": "^7.25.7", - "@swc/core": "^1.7.36" + "@swc/core": "^1.10.14" }, "devDependencies": { "@babel/parser": "^7.26.7", "@babel/plugin-syntax-import-assertions": "^7.26.0", "@babel/traverse": "^7.26.7", - "@swc/core": "^1.10.12", + "@swc/core": "^1.10.14", "@types/inquirer": "^9.0.7", "@types/mocha": "^10.0.10", "@web/dev-server": "^0.4.6", diff --git a/packages-node/providence-analytics/src/program/analyzers/find-classes.js b/packages-node/providence-analytics/src/program/analyzers/find-classes.js index 59474b2fd6..5793795128 100644 --- a/packages-node/providence-analytics/src/program/analyzers/find-classes.js +++ b/packages-node/providence-analytics/src/program/analyzers/find-classes.js @@ -1,11 +1,21 @@ /* eslint-disable no-shadow, no-param-reassign */ import path from 'path'; -import { oxcTraverse, isProperty } from '../utils/oxc-traverse.js'; - import { trackDownIdentifierFromScope } from '../utils/track-down-identifier.js'; +import { + expressionOf, + isProperty, + isSetter, + isGetter, + isStatic, + nameOf, + idOf, +} from '../utils/ast-normalizations.js'; +import { oxcTraverse } from '../utils/oxc-traverse.js'; import { Analyzer } from '../core/Analyzer.js'; +import { isCustomElementsGet } from './find-customelements.js'; + /** * @typedef {import('@babel/types').File} File * @typedef {import('@babel/types').ClassMethod} ClassMethod @@ -16,22 +26,27 @@ import { Analyzer } from '../core/Analyzer.js'; * @typedef {import('../../../types/index.js').FindClassesAnalyzerEntry} FindClassesAnalyzerEntry * @typedef {import('../../../types/index.js').FindClassesConfig} FindClassesConfig * @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst + * @typedef {import("@swc/core").Node} SwcNode */ /** * Finds import specifiers and sources - * @param {File} babelAst + * @param {File} oxcAst * @param {string} fullCurrentFilePath the file being currently processed */ -async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath) { +async function findMembersPerAstEntry(oxcAst, fullCurrentFilePath, projectPath) { // The transformed entry const classesFound = []; /** * Detects private/publicness based on underscores. Checks '$' as well * @param {string} name - * @returns {'public'|'protected'|'private'} + * @returns {'public'|'protected'|'private'|'[n/a]'} */ function computeAccessType(name) { + if (name === 'constructor') { + return '[n/a]'; + } + if (name.startsWith('_') || name.startsWith('$')) { // (at least) 2 prefixes if (name.startsWith('__') || name.startsWith('$$')) { @@ -47,7 +62,7 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath * @returns */ function isStaticProperties({ node }) { - return node.static && node.kind === 'get' && node.key.name === 'properties'; + return isStatic(node) && isGetter(node) && nameOf(node.key) === 'properties'; } // function isBlacklisted({ node }) { @@ -86,26 +101,47 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath // } /** - * + * @param {SwcNode|OxcNode} node + */ + function isSuperClassAMixin(superClassNode) { + if (!superClassNode) return false; + + const isCallExpression = superClassNode?.type === 'CallExpression'; + if (!isCallExpression) return false; + return !isCustomElementsGet(superClassNode.callee); + } + + /** * @param {NodePath} astPath * @param {{isMixin?:boolean}} opts */ async function traverseClass(astPath, { isMixin = false } = {}) { const classRes = {}; - classRes.name = astPath.node.id && astPath.node.id.name; + classRes.name = (idOf(astPath.node) && nameOf(idOf(astPath.node))) || null; classRes.isMixin = Boolean(isMixin); + if (astPath.node.superClass) { const superClasses = []; // Add all Identifier names let parent = astPath.node.superClass; - while (parent.type === 'CallExpression') { - superClasses.push({ name: parent.callee.name, isMixin: true }); + while (isSuperClassAMixin(parent)) { + superClasses.push({ name: nameOf(parent.callee), isMixin: true }); // As long as we are a CallExpression, we will have a parent - [parent] = parent.arguments; + [parent] = parent.arguments.map(expressionOf); + } + + // At the end of the chain, we find type === Identifier or customElements.get directly. + if (isCustomElementsGet(parent.callee)) { + superClasses.push({ + name: null, + customElementsGetRef: nameOf(parent.arguments?.map(expressionOf)[0]), + isMixin: false, + }); + } else { + // an identifier like 'MyClass' + superClasses.push({ name: nameOf(expressionOf(parent)), isMixin: false }); } - // At the end of the chain, we find type === Identifier - superClasses.push({ name: parent.name, isMixin: false }); // For all found superclasses, track down their root location. // This will either result in a local, relative astPath in the project, @@ -160,19 +196,27 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath } const methodRes = {}; - const { name } = astPath.node.key; + const name = nameOf(astPath.node.key); methodRes.name = name; methodRes.accessType = computeAccessType(name); + if ( + ['constructor', 'connectedCallback', 'disconnectedCallback', 'adoptedCallback'].includes( + name, + ) + ) { + methodRes.isPartOfPlatformLifeCycle = true; + } - if (astPath.node.kind === 'set' || astPath.node.kind === 'get') { - if (astPath.node.static) { + if (isSetter(astPath.node) || isGetter(astPath.node)) { + const setOrGet = isSetter(astPath.node) ? 'set' : 'get'; + if (isStatic(astPath.node)) { methodRes.static = true; } - methodRes.kind = [...(methodRes.kind || []), astPath.node.kind]; + methodRes.kind = [...(methodRes.kind || []), setOrGet]; // Merge getter/setters into one - const found = classRes.members.props.find(p => p.name === name); + const found = classRes.members.props.find(p => nameOf(p) === name); if (found) { - found.kind = [...(found.kind || []), astPath.node.kind]; + found.kind = [...(found.kind || []), setOrGet]; } else { classRes.members.props.push(methodRes); } @@ -184,6 +228,8 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath astPath.traverse({ ClassMethod: handleMethodDefinitionOrClassMethod, MethodDefinition: handleMethodDefinitionOrClassMethod, + // for swc + Constructor: handleMethodDefinitionOrClassMethod, }); classesFound.push(classRes); @@ -191,7 +237,7 @@ async function findMembersPerAstEntry(babelAst, fullCurrentFilePath, projectPath const classesToTraverse = []; - oxcTraverse(babelAst, { + oxcTraverse(oxcAst, { ClassDeclaration(astPath) { classesToTraverse.push({ astPath, isMixin: false }); }, diff --git a/packages-node/providence-analytics/src/program/analyzers/find-customelements.js b/packages-node/providence-analytics/src/program/analyzers/find-customelements.js index bd1cd587b7..b5fcae26d6 100644 --- a/packages-node/providence-analytics/src/program/analyzers/find-customelements.js +++ b/packages-node/providence-analytics/src/program/analyzers/find-customelements.js @@ -1,16 +1,56 @@ import path from 'path'; // import babelTraverse from '@babel/traverse'; -import { oxcTraverse } from '../utils/oxc-traverse.js'; - +import { isLiteral, nameOf, expressionOf } from '../utils/ast-normalizations.js'; import { trackDownIdentifierFromScope } from '../utils/track-down-identifier.js'; +import { oxcTraverse } from '../utils/oxc-traverse.js'; import { Analyzer } from '../core/Analyzer.js'; /** * @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName * @typedef {import('@babel/types').File} File + * @typedef {import("@swc/core").Node} SwcNode + */ + +/** + * @param {SwcNode} node + * @returns {boolean} + */ +export function isCustomElementsObj(node) { + if (!node) return false; + + return ( + // @ts-expect-error + nameOf(node.object) === 'customElements' || + // @ts-expect-error + (nameOf(node.object?.object) === 'window' && + // @ts-expect-error + nameOf(node.object?.property) === 'customElements') + ); +} + +/** + * @param {SwcNode} node + * @returns {boolean} */ +export function isCustomElementsGet(node) { + if (!node) return false; + + // @ts-expect-error + return isCustomElementsObj(node) && nameOf(node.property) === 'get'; +} + +/** + * @param {SwcNode} node + * @returns {boolean} + */ +export function isCustomElementsDefine(node) { + if (!node) return false; + + // @ts-expect-error + return isCustomElementsObj(node) && nameOf(node.property) === 'define'; +} function cleanup(transformedEntry) { transformedEntry.forEach(definitionObj => { @@ -44,47 +84,39 @@ async function trackdownRoot(transformedEntry, relativePath, projectPath) { */ function findCustomElementsPerAstFile(oxcAst) { const definitions = []; + oxcTraverse(oxcAst, { CallExpression(astPath) { - let found = false; + let didFindCeDefine = false; + const onMemberExpr = memberPath => { + if (memberPath.node !== astPath.node.callee) return; + + if (isCustomElementsDefine(memberPath.node)) { + didFindCeDefine = true; + } + }; // Doing it like this we detect 'customElements.define()', // but also 'window.customElements.define()' astPath.traverse({ - // MemberExpression in babel - StaticMemberExpression(memberPath) { - if (memberPath.node !== astPath.node.callee) { - return; - } - - const { node } = memberPath; - - if (node.object.name === 'customElements' && node.property.name === 'define') { - found = true; - } - if ( - node.object.object?.name === 'window' && - node.object.property.name === 'customElements' && - node.property.name === 'define' - ) { - found = true; - } - }, + StaticMemberExpression: onMemberExpr, + // MemberExpression in babel and swc + MemberExpression: onMemberExpr, }); - if (found) { + if (didFindCeDefine) { let tagName; let constructorIdentifier; - if ( - astPath.node.arguments[0].type === 'StringLiteral' || - astPath.node.arguments[0].type === 'Literal' - ) { - tagName = astPath.node.arguments[0].value; + const firstArg = astPath.node.arguments[0]; + if (isLiteral(expressionOf(firstArg))) { + tagName = nameOf(expressionOf(firstArg)); } else { // No Literal found. For now, we only mark them as '[variable]' tagName = '[variable]'; } - if (astPath.node.arguments[1].type === 'Identifier') { - constructorIdentifier = astPath.node.arguments[1].name; + + const secondArg = expressionOf(astPath.node.arguments[1]); + if (secondArg.type === 'Identifier') { + constructorIdentifier = nameOf(secondArg); } else { // We assume customElements.define('my-el', class extends HTMLElement {...}) constructorIdentifier = '[inline]'; diff --git a/packages-node/providence-analytics/src/program/analyzers/find-imports.js b/packages-node/providence-analytics/src/program/analyzers/find-imports.js index c33b66333c..61f23d7e4e 100644 --- a/packages-node/providence-analytics/src/program/analyzers/find-imports.js +++ b/packages-node/providence-analytics/src/program/analyzers/find-imports.js @@ -2,10 +2,10 @@ import { normalizeSourcePaths } from './helpers/normalize-source-paths.js'; import { isRelativeSourcePath } from '../utils/relative-source-path.js'; import { getAssertionType } from '../utils/get-assertion-type.js'; +import { isLiteral } from '../utils/ast-normalizations.js'; import { oxcTraverse } from '../utils/oxc-traverse.js'; import { LogService } from '../core/LogService.js'; import { Analyzer } from '../core/Analyzer.js'; - /** * @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot * @typedef {import('../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult @@ -17,14 +17,6 @@ import { Analyzer } from '../core/Analyzer.js'; * @typedef {import("@swc/core").Node} SwcNode */ -/** - * @param {SwcNode|undefined} node - * @returns {boolean} - */ -function isLiteral(node) { - return node?.type === 'Literal' || node?.type === 'StringLiteral'; -} - /** * Intends to work for oxc, swc, and babel asts * @param {SwcNode} s diff --git a/packages-node/providence-analytics/src/program/analyzers/match-imports.js b/packages-node/providence-analytics/src/program/analyzers/match-imports.js index 50dab503a4..869b128eb9 100644 --- a/packages-node/providence-analytics/src/program/analyzers/match-imports.js +++ b/packages-node/providence-analytics/src/program/analyzers/match-imports.js @@ -209,6 +209,7 @@ export default class MatchImportsAnalyzer extends Analyzer { skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility, suppressNonCriticalLogs: true, gatherFilesConfig: cfg.gatherFilesConfigReference, + parser: cfg.parser, }); } @@ -221,6 +222,7 @@ export default class MatchImportsAnalyzer extends Analyzer { skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility, suppressNonCriticalLogs: true, gatherFilesConfig: cfg.gatherFilesConfig, + parser: cfg.parser, }); } diff --git a/packages-node/providence-analytics/src/program/core/Analyzer.js b/packages-node/providence-analytics/src/program/core/Analyzer.js index f208287ca8..dec10f4b68 100644 --- a/packages-node/providence-analytics/src/program/core/Analyzer.js +++ b/packages-node/providence-analytics/src/program/core/Analyzer.js @@ -52,8 +52,8 @@ async function analyzePerAstFile(projectData, astAnalysis, analyzerCfg) { try { const { result, meta } = await astAnalysis(ast, context); entries.push({ file: relativePath, meta, result }); - } catch (e) { - LogService.error(`[analyzePerAstFile]: ${fullPath} throws: ${e}`); + } catch (/** @type {* & Error} */ e) { + LogService.error(`[analyzePerAstFile]: ${fullPath} throws: ${e.message}. Cause: ${e.cause}`); } } const filteredEntries = entries.filter(({ result }) => Boolean(result.length)); @@ -377,7 +377,7 @@ export class Analyzer { */ const astDataProjects = await QueryService.addAstToProjectsData( finalTargetData, - this.requiredAst, + analyzeFileCfg.config.parser || this.requiredAst, ); return analyzePerAstFile( astDataProjects[0], @@ -411,7 +411,7 @@ export class Analyzer { projectPath: cfg.targetProjectPath, filePaths: cfg.targetFilePaths, targetData: this.targetData, - config: this.config, + config: cfg, }); /** diff --git a/packages-node/providence-analytics/src/program/providence.js b/packages-node/providence-analytics/src/program/providence.js index 6e078ee587..c176f32582 100644 --- a/packages-node/providence-analytics/src/program/providence.js +++ b/packages-node/providence-analytics/src/program/providence.js @@ -88,6 +88,7 @@ async function handleAnalyzerForProjectCombo(slicedQConfig, cfg) { gatherFilesConfigReference: cfg.gatherFilesConfigReference, skipCheckMatchCompatibility: cfg.skipCheckMatchCompatibility, addSystemPathsInResult: cfg.addSystemPathsInResult, + parser: cfg.parser, ...slicedQConfig.analyzerConfig, }); diff --git a/packages-node/providence-analytics/src/program/utils/ast-normalizations.js b/packages-node/providence-analytics/src/program/utils/ast-normalizations.js new file mode 100644 index 0000000000..2fd76f4bd8 --- /dev/null +++ b/packages-node/providence-analytics/src/program/utils/ast-normalizations.js @@ -0,0 +1,110 @@ +/** + * @typedef {import('../../../types/index.js').SwcTraversalContext} SwcTraversalContext + * @typedef {import('@swc/core').VariableDeclarator} SwcVariableDeclarator + * @typedef {import('../../../types/index.js').SwcVisitor} SwcVisitor + * @typedef {import('../../../types/index.js').SwcBinding} SwcBinding + * @typedef {import('../../../types/index.js').SwcScope} SwcScope + * @typedef {import('../../../types/index.js').SwcPath} SwcPath + * @typedef {import("oxc-parser").ParseResult} OxcParseResult + * @typedef {import('@swc/core').Identifier} SwcIdentifierNode + * @typedef {import('oxc-parser').ParseResult} OxcNode + * @typedef {import('@swc/core').Module} SwcAstModule + * @typedef {import('@swc/core').Node} SwcNode + */ + +/** + * @param {SwcNode|undefined} node + * @returns {boolean} + */ +export function isLiteral(node) { + return node?.type === 'Literal' || node?.type === 'StringLiteral'; +} + +/** + * @param {SwcNode|OxcNode} node + */ +export function nameOf(node) { + // @ts-expect-error + return node?.value || node?.name; +} + +/** + * @param {SwcNode|OxcNode} node + */ +export function idOf(node) { + // @ts-expect-error + return node?.id || node?.identifier; +} + +/** + * @param {SwcNode|OxcNode} node + */ +export function importedOf(node) { + // @ts-expect-error + // babel/oxc vs swc + return node?.imported || node?.orig || node?.local; +} + +/** + * @param {SwcNode|OxcNode} node + */ +export function isProperty(node) { + if (!node) return false; + + switch (node.type) { + case 'ObjectProperty': + case 'ClassProperty': + case 'ClassAccessorProperty': + case 'ClassPrivateProperty': + break; + default: + return false; + } + + return false; +} + +/** + * @param {{ kind: string; }} node + */ +export function isSetter(node) { + return node?.kind === 'set' || node?.kind === 'setter'; +} + +/** + * @param {{ kind: string; }} node + */ +export function isGetter(node) { + return node?.kind === 'get' || node?.kind === 'getter'; +} + +/** + * @param {{ static?: boolean; isStatic?: boolean; }} node + */ +export function isStatic(node) { + return node?.static || node?.isStatic; +} + +/** + * Swc wraps `.callee` or arguments in expressions... Normalize... + * @param {SwcNode|OxcNode} node + * @returns {SwcNode|OxcNode} + */ +export function expressionOf(node) { + // @ts-expect-error + return node.expression || node; +} + +/** + * @param {SwcNode|OxcNode} node + * @returns {boolean} + */ +export function isConstructor(node) { + return ( + // @ts-expect-error + // for swc + node?.type === 'Constructor' || + // @ts-expect-error + (node?.type === 'MethodDefinition' && nameOf(node?.key) === 'constructor') + ); +} diff --git a/packages-node/providence-analytics/src/program/utils/get-source-code-fragment-of-declaration.js b/packages-node/providence-analytics/src/program/utils/get-source-code-fragment-of-declaration.js index d797bcd7ee..0dc6c85f76 100644 --- a/packages-node/providence-analytics/src/program/utils/get-source-code-fragment-of-declaration.js +++ b/packages-node/providence-analytics/src/program/utils/get-source-code-fragment-of-declaration.js @@ -1,9 +1,10 @@ import path from 'path'; -import { oxcTraverse, getPathFromNode, nameOf } from './oxc-traverse.js'; +import { oxcTraverse, getPathFromNode } from './oxc-traverse.js'; import { trackDownIdentifier } from './track-down-identifier.js'; import { AstService } from '../core/AstService.js'; import { toPosixPath } from './to-posix-path.js'; +import { nameOf } from './ast-normalizations.js'; import { fsAdapter } from './fs-adapter.js'; /** diff --git a/packages-node/providence-analytics/src/program/utils/oxc-traverse.js b/packages-node/providence-analytics/src/program/utils/oxc-traverse.js index 159b10dbc6..59ea40e8bb 100644 --- a/packages-node/providence-analytics/src/program/utils/oxc-traverse.js +++ b/packages-node/providence-analytics/src/program/utils/oxc-traverse.js @@ -12,11 +12,13 @@ * @typedef {import('@swc/core').Node} SwcNode */ +import { nameOf } from './ast-normalizations.js'; + /** * Contains all node info, to create paths from * @type {WeakMap} */ -const swcPathCache = new WeakMap(); +const oxcPathCache = new WeakMap(); const fnTypes = [ 'FunctionDeclaration', @@ -28,42 +30,6 @@ const fnTypes = [ const nonBlockParentTypes = [...fnTypes, 'SwitchStatement', 'ClassDeclaration']; -/** - * @param {SwcNode|OxcNode} node - */ -export function nameOf(node) { - // @ts-expect-error - return node.value || node.name; -} - -/** - * @param {SwcNode|OxcNode} node - */ -export function importedOf(node) { - // @ts-expect-error - // babel/oxc vs swc - return node?.imported || node?.orig || node?.local; -} - -/** - * @param {SwcNode|OxcNode} node - */ -export function isProperty(node) { - if (!node) return false; - - switch (node.type) { - case 'ObjectProperty': - case 'ClassProperty': - case 'ClassAccessorProperty': - case 'ClassPrivateProperty': - break; - default: - return false; - } - - return false; -} - /** * @param {SwcPath} swcPath * @param {SwcScope} currentScope @@ -107,7 +73,7 @@ function getNewScope(swcPath, currentScope, traversalContext) { * @param {SwcNode} node */ export function getPathFromNode(node) { - return swcPathCache.get(node); + return oxcPathCache.get(node); } /** @@ -144,7 +110,7 @@ function createSwcPath(node, parent, stop, scope) { return oxcTraverse(node, visitor); }, }; - swcPathCache.set(node, swcPath); + oxcPathCache.set(node, swcPath); return swcPath; } @@ -159,7 +125,7 @@ function createSwcPath(node, parent, stop, scope) { function isBindingNode(parent, identifierName) { if (['VariableDeclarator', 'ClassDeclaration'].includes(parent.type)) { // @ts-expect-error - return nameOf(parent.id) === identifierName; + return nameOf(parent.id || parent.identifier) === identifierName; } return [ 'ArrowFunctionExpression', @@ -252,6 +218,18 @@ function isRootNode(node) { return node.type === 'Program' || node.type === 'Module' || node.type === 'Script'; } +/** + * Swc wraps arguments in expressions, whereas oxc doesn't.. + * @param {SwcNode} potentialSwcArg + * @returns {SwcNode} + */ +function normalizePotentialSwcArgument(potentialSwcArg) { + // @ts-expect-error + const isSwcArgument = 'spread' in potentialSwcArg && potentialSwcArg.expression; + // @ts-expect-error + return isSwcArgument ? potentialSwcArg.expression : potentialSwcArg; +} + /** * @param {{node: SwcNode; }} node * @param {(data:{child:SwcNode}) => void} callback @@ -265,7 +243,7 @@ const loopChildren = ({ node }, callback) => { if (Array.isArray(childVal)) { for (const childValElem of childVal) { - callback({ child: childValElem }); + callback({ child: normalizePotentialSwcArgument(childValElem) }); } } else if (typeof childVal === 'object') { callback({ child: childVal }); @@ -335,7 +313,7 @@ export function oxcTraverse(oxcAst, visitor, { needsAdvancedPaths = false } = {} */ const handlePathAndScope = (node, parent, scope, hasPreparedTree, traversalContext) => { if (hasPreparedTree) { - const swcPath = /** @type {SwcPath} */ (swcPathCache.get(node)); + const swcPath = /** @type {SwcPath} */ (oxcPathCache.get(node)); return { swcPath, newOrCurScope: getNewScope(swcPath, scope, traversalContext) || scope, diff --git a/packages-node/providence-analytics/src/program/utils/track-down-identifier.js b/packages-node/providence-analytics/src/program/utils/track-down-identifier.js index 029deadb2b..2202aaebbd 100644 --- a/packages-node/providence-analytics/src/program/utils/track-down-identifier.js +++ b/packages-node/providence-analytics/src/program/utils/track-down-identifier.js @@ -3,8 +3,9 @@ import path from 'path'; import { isRelativeSourcePath, toRelativeSourcePath } from './relative-source-path.js'; import { InputDataService } from '../core/InputDataService.js'; import { resolveImportPath } from './resolve-import-path.js'; +import { nameOf, importedOf } from './ast-normalizations.js'; import { AstService } from '../core/AstService.js'; -import { oxcTraverse, nameOf, importedOf } from './oxc-traverse.js'; +import { oxcTraverse } from './oxc-traverse.js'; import { fsAdapter } from './fs-adapter.js'; import { memoize } from './memoize.js'; diff --git a/packages-node/providence-analytics/test-helpers/project-mocks-analyzer-outputs/find-classes.json b/packages-node/providence-analytics/test-helpers/project-mocks-analyzer-outputs/find-classes.json index 49a3d0e115..0b1710f848 100644 --- a/packages-node/providence-analytics/test-helpers/project-mocks-analyzer-outputs/find-classes.json +++ b/packages-node/providence-analytics/test-helpers/project-mocks-analyzer-outputs/find-classes.json @@ -44,15 +44,12 @@ "isMixin": false, "superClasses": [ { - "isMixin": true, - "rootFile": { - "file": "[current]" - } - }, - { + "name": null, + "customElementsGetRef": "on-the-fly", "isMixin": false, "rootFile": { - "file": "[current]" + "file": "[current]", + "specifier": null } } ], @@ -151,14 +148,17 @@ "accessType": "private" }, { + "isPartOfPlatformLifeCycle": true, "name": "constructor", - "accessType": "public" + "accessType": "[n/a]" }, { + "isPartOfPlatformLifeCycle": true, "name": "connectedCallback", "accessType": "public" }, { + "isPartOfPlatformLifeCycle": true, "name": "disconnectedCallback", "accessType": "public" }, diff --git a/packages-node/providence-analytics/test-node/program/analyzers/e2e/.gitignore b/packages-node/providence-analytics/test-node/program/analyzers/e2e/.gitignore new file mode 100644 index 0000000000..87a45b5d2f --- /dev/null +++ b/packages-node/providence-analytics/test-node/program/analyzers/e2e/.gitignore @@ -0,0 +1 @@ +__providence-output \ No newline at end of file diff --git a/packages-node/providence-analytics/test-node/program/analyzers/e2e/all-analyzers.e2e.js b/packages-node/providence-analytics/test-node/program/analyzers/e2e/all-analyzers.e2e.js index e2513e54b4..e68d14695d 100644 --- a/packages-node/providence-analytics/test-node/program/analyzers/e2e/all-analyzers.e2e.js +++ b/packages-node/providence-analytics/test-node/program/analyzers/e2e/all-analyzers.e2e.js @@ -1,6 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies */ -import pathLib, { dirname } from 'path'; -import { fileURLToPath } from 'url'; +import path from 'path'; +import url from 'url'; import { expect } from 'chai'; import { it } from 'mocha'; import { providence } from '../../../../src/program/providence.js'; @@ -9,9 +9,9 @@ import { ReportService } from '../../../../src/program/core/ReportService.js'; import { memoize } from '../../../../src/program/utils/memoize.js'; import { setupAnalyzerTest } from '../../../../test-helpers/setup-analyzer-test.js'; import { + MatchImportsAnalyzer, FindExportsAnalyzer, FindImportsAnalyzer, - 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'; @@ -24,130 +24,146 @@ import { fsAdapter } from '../../../../src/program/utils/fs-adapter.js'; * @typedef {import('../../../../types/index.js').QueryResult} QueryResult */ -const __dirname = dirname(fileURLToPath(import.meta.url)); +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); -setupAnalyzerTest(); +/** + * @param {{parser: 'oxc'|'swc'}} opts + */ +function runE2eSuite({ parser }) { + setupAnalyzerTest(); -describe('Analyzers file-system integration', () => { - /** - * Flag to enable mode that generates e2e mocks. - * We 'abuse' this test file for that purpose for ease of maintenance - * @type {boolean} - */ - const generateE2eMode = process.argv.includes('--generate-e2e-mode'); + describe('Analyzers file-system integration', () => { + /** + * Flag to enable mode that generates e2e mocks. + * We 'abuse' this test file for that purpose for ease of maintenance + * @type {boolean} + */ + const generateE2eMode = process.argv.includes('--generate-e2e-mode'); - const targetPath = pathLib.resolve( - __dirname, - '../../../../test-helpers/project-mocks/importing-target-project', - ); - const referencePath = pathLib.resolve( - __dirname, - `../../../../test-helpers/project-mocks/importing-target-project/node_modules/exporting-ref-project`, - ); + const targetPath = path.resolve( + __dirname, + '../../../../test-helpers/project-mocks/importing-target-project', + ); + const referencePath = path.resolve( + __dirname, + `../../../../test-helpers/project-mocks/importing-target-project/node_modules/exporting-ref-project`, + ); - const originalGetResultFileNameAndPath = ReportService._getResultFileNameAndPath; - const originalOutputPath = ReportService.outputPath; + const originalGetResultFileNameAndPath = ReportService._getResultFileNameAndPath; + const originalOutputPath = ReportService.outputPath; - const originalGetCachedResult = ReportService.getCachedResult; + const originalGetCachedResult = ReportService.getCachedResult; - const memoizeCacheEnabledInitial = memoize.isCacheEnabled; - memoize.disableCaching(); + const memoizeCacheEnabledInitial = memoize.isCacheEnabled; + memoize.disableCaching(); - after(() => { - ReportService._getResultFileNameAndPath = originalGetResultFileNameAndPath; - ReportService.getCachedResult = originalGetCachedResult; - ReportService.outputPath = originalOutputPath; - memoize.restoreCaching(memoizeCacheEnabledInitial); - }); + after(() => { + ReportService._getResultFileNameAndPath = originalGetResultFileNameAndPath; + ReportService.getCachedResult = originalGetCachedResult; + ReportService.outputPath = originalOutputPath; + memoize.restoreCaching(memoizeCacheEnabledInitial); + }); - if (generateE2eMode) { - ReportService.outputPath = pathLib.resolve( - __dirname, - '../../../../test-helpers/project-mocks-analyzer-outputs', - ); - // @ts-expect-error - // eslint-disable-next-line func-names - ReportService._getResultFileNameAndPath = function (name) { - return pathLib.join(this.outputPath, `${name}.json`); - }; - } else { - ReportService.outputPath = __dirname; // prevents cache to fail the test - // @ts-ignore - ReportService.getCachedResult = () => undefined; - } - const analyzers = [ - { - providenceConfig: { - targetProjectPaths: [targetPath], + if (generateE2eMode) { + ReportService.outputPath = path.resolve( + __dirname, + '../../../../test-helpers/project-mocks-analyzer-outputs', + ); + // @ts-expect-error + // eslint-disable-next-line func-names + ReportService._getResultFileNameAndPath = function (name) { + return path.join(this.outputPath, `${name}.json`); + }; + } else { + ReportService.outputPath = path.join(__dirname, '__providence-output'); // prevents cache to fail the test + // @ts-ignore + ReportService.getCachedResult = () => undefined; + } + const analyzers = [ + { + providenceConfig: { + targetProjectPaths: [targetPath], + parser, + }, + ctor: FindCustomelementsAnalyzer, }, - ctor: FindCustomelementsAnalyzer, - }, - { - providenceConfig: { - targetProjectPaths: [targetPath], + { + providenceConfig: { + targetProjectPaths: [targetPath], + parser, + }, + ctor: FindImportsAnalyzer, }, - ctor: FindImportsAnalyzer, - }, - { - providenceConfig: { - targetProjectPaths: [referencePath], + { + providenceConfig: { + targetProjectPaths: [referencePath], + parser, + }, + ctor: FindExportsAnalyzer, }, - ctor: FindExportsAnalyzer, - }, - { - providenceConfig: { - targetProjectPaths: [targetPath], + { + providenceConfig: { + targetProjectPaths: [targetPath], + parser, + }, + ctor: FindClassesAnalyzer, }, - ctor: FindClassesAnalyzer, - }, - { - providenceConfig: { - targetProjectPaths: [targetPath], - referenceProjectPaths: [referencePath], + { + providenceConfig: { + targetProjectPaths: [targetPath], + referenceProjectPaths: [referencePath], + parser, + }, + ctor: MatchImportsAnalyzer, }, - ctor: MatchImportsAnalyzer, - }, - { - providenceConfig: { - targetProjectPaths: [targetPath], - referenceProjectPaths: [referencePath], + { + providenceConfig: { + targetProjectPaths: [targetPath], + referenceProjectPaths: [referencePath], + parser, + }, + ctor: MatchSubclassesAnalyzer, }, - ctor: MatchSubclassesAnalyzer, - }, - { - providenceConfig: { - targetProjectPaths: [targetPath], - referenceProjectPaths: [referencePath], + { + providenceConfig: { + targetProjectPaths: [targetPath], + referenceProjectPaths: [referencePath], + parser, + }, + ctor: MatchPathsAnalyzer, }, - ctor: MatchPathsAnalyzer, - }, - ]; + ]; - for (const { ctor, providenceConfig } of analyzers) { - it(`"${ctor.analyzerName}" analyzer`, async () => { - const currentQueryConfig = await QueryService.getQueryConfigFromAnalyzer(ctor); - const queryResults = await providence( - currentQueryConfig, - /** @type {ProvidenceConfig} */ (providenceConfig), - ); - if (generateE2eMode) { - console.info( - 'Successfully created mocks. Do not forget to rerun tests now without "--generate-e2e-mode"', + for (const { ctor, providenceConfig } of analyzers) { + it(`"${ctor.analyzerName}" analyzer (${parser})`, async () => { + const currentQueryConfig = await QueryService.getQueryConfigFromAnalyzer(ctor); + const queryResults = await providence( + currentQueryConfig, + /** @type {ProvidenceConfig} */ (providenceConfig), ); - return; - } - const expectedOutput = JSON.parse( - fsAdapter.fs.readFileSync( - pathLib.resolve( - __dirname, - `../../../../test-helpers/project-mocks-analyzer-outputs/${ctor.analyzerName}.json`, + if (generateE2eMode) { + console.info( + 'Successfully created mocks. Do not forget to rerun tests now without "--generate-e2e-mode"', + ); + return; + } + const expectedOutput = JSON.parse( + fsAdapter.fs.readFileSync( + path.resolve( + __dirname, + `../../../../test-helpers/project-mocks-analyzer-outputs/${ctor.analyzerName}.json`, + ), + 'utf8', ), - 'utf8', - ), - ); - const { queryOutput } = JSON.parse(JSON.stringify(queryResults[0])); - // expect(queryOutput).not.to.deep.equal([]); - expect(queryOutput).to.deep.equal(expectedOutput.queryOutput); - }); - } -}); + ); + const { queryOutput } = JSON.parse(JSON.stringify(queryResults[0])); + // expect(queryOutput).not.to.deep.equal([]); + expect(queryOutput).to.deep.equal(expectedOutput.queryOutput); + }); + } + }); +} + +// TODO: find out why order matters here... +runE2eSuite({ parser: 'swc' }); +runE2eSuite({ parser: 'oxc' }); diff --git a/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-classes_-_importing-target-project_0.0.2-target-mock__-905964591.json b/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-classes_-_importing-target-project_0.0.2-target-mock__-905964591.json deleted file mode 100644 index 51181c2007..0000000000 --- a/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-classes_-_importing-target-project_0.0.2-target-mock__-905964591.json +++ /dev/null @@ -1,220 +0,0 @@ -{ - "meta": { - "searchType": "ast-analyzer", - "analyzerMeta": { - "name": "find-classes", - "requiredAst": "oxc", - "identifier": "importing-target-project_0.0.2-target-mock__-905964591", - "targetProject": { - "mainEntry": "./target-src/match-imports/root-level-imports.js", - "name": "importing-target-project", - "version": "0.0.2-target-mock", - "commitHash": "[not-a-git-root]" - }, - "configuration": { - "gatherFilesConfig": {}, - "skipCheckMatchCompatibility": false, - "addSystemPathsInResult": false - } - } - }, - "queryOutput": [ - { - "file": "./target-src/find-customelements/multiple.js", - "result": [ - { - "name": null, - "isMixin": true, - "superClasses": [ - { - "name": "HTMLElement", - "isMixin": false, - "rootFile": { - "file": "[current]", - "specifier": "HTMLElement" - } - } - ], - "members": { - "props": [], - "methods": [] - } - }, - { - "name": "ExtendedOnTheFly", - "isMixin": false, - "superClasses": [ - { - "isMixin": true, - "rootFile": { - "file": "[current]" - } - }, - { - "isMixin": false, - "rootFile": { - "file": "[current]" - } - } - ], - "members": { - "props": [], - "methods": [] - } - } - ] - }, - { - "file": "./target-src/match-subclasses/ExtendedComp.js", - "result": [ - { - "name": "ExtendedComp", - "isMixin": false, - "superClasses": [ - { - "name": "MyCompMixin", - "isMixin": true, - "rootFile": { - "file": "exporting-ref-project", - "specifier": "[default]" - } - }, - { - "name": "RefClass", - "isMixin": false, - "rootFile": { - "file": "exporting-ref-project", - "specifier": "RefClass" - } - } - ], - "members": { - "props": [ - { - "name": "getterSetter", - "accessType": "public", - "kind": [ - "get", - "set" - ] - }, - { - "name": "staticGetterSetter", - "accessType": "public", - "static": true, - "kind": [ - "get", - "set" - ] - }, - { - "name": "attributes", - "accessType": "public", - "static": true, - "kind": [ - "get" - ] - }, - { - "name": "styles", - "accessType": "public", - "static": true, - "kind": [ - "get" - ] - }, - { - "name": "updateComplete", - "accessType": "public", - "kind": [ - "get" - ] - }, - { - "name": "localizeNamespaces", - "accessType": "public", - "static": true, - "kind": [ - "get" - ] - }, - { - "name": "slots", - "accessType": "public", - "kind": [ - "get" - ] - } - ], - "methods": [ - { - "name": "method", - "accessType": "public" - }, - { - "name": "_protectedMethod", - "accessType": "protected" - }, - { - "name": "__privateMethod", - "accessType": "private" - }, - { - "name": "$protectedMethod", - "accessType": "protected" - }, - { - "name": "$$privateMethod", - "accessType": "private" - }, - { - "name": "constructor", - "accessType": "public" - }, - { - "name": "connectedCallback", - "accessType": "public" - }, - { - "name": "disconnectedCallback", - "accessType": "public" - }, - { - "name": "requestUpdate", - "accessType": "public" - }, - { - "name": "createRenderRoot", - "accessType": "public" - }, - { - "name": "render", - "accessType": "public" - }, - { - "name": "updated", - "accessType": "public" - }, - { - "name": "firstUpdated", - "accessType": "public" - }, - { - "name": "update", - "accessType": "public" - }, - { - "name": "shouldUpdate", - "accessType": "public" - }, - { - "name": "onLocaleUpdated", - "accessType": "public" - } - ] - } - } - ] - } - ] -} \ No newline at end of file diff --git a/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-customelements_-_importing-target-project_0.0.2-target-mock__61665553.json b/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-customelements_-_importing-target-project_0.0.2-target-mock__61665553.json deleted file mode 100644 index 9dc5643ebe..0000000000 --- a/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-customelements_-_importing-target-project_0.0.2-target-mock__61665553.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "meta": { - "searchType": "ast-analyzer", - "analyzerMeta": { - "name": "find-customelements", - "requiredAst": "oxc", - "identifier": "importing-target-project_0.0.2-target-mock__61665553", - "targetProject": { - "mainEntry": "./target-src/match-imports/root-level-imports.js", - "name": "importing-target-project", - "version": "0.0.2-target-mock", - "commitHash": "[not-a-git-root]" - }, - "configuration": { - "gatherFilesConfig": {}, - "skipCheckMatchCompatibility": false, - "addSystemPathsInResult": false - } - } - }, - "queryOutput": [ - { - "file": "./target-src/find-customelements/multiple.js", - "result": [ - { - "tagName": "ref-class", - "constructorIdentifier": "RefClass", - "rootFile": { - "file": "exporting-ref-project", - "specifier": "RefClass" - } - }, - { - "tagName": "extended-comp", - "constructorIdentifier": "ExtendedComp", - "rootFile": { - "file": "./target-src/match-subclasses/ExtendedComp.js", - "specifier": "ExtendedComp" - } - }, - { - "tagName": "on-the-fly", - "constructorIdentifier": "[inline]", - "rootFile": { - "file": "[current]", - "specifier": "[inline]" - } - } - ] - } - ] -} \ No newline at end of file diff --git a/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-exports_-_exporting-ref-project_1.0.0__-42206859.json b/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-exports_-_exporting-ref-project_1.0.0__-42206859.json deleted file mode 100644 index 7291ada15d..0000000000 --- a/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-exports_-_exporting-ref-project_1.0.0__-42206859.json +++ /dev/null @@ -1,194 +0,0 @@ -{ - "meta": { - "searchType": "ast-analyzer", - "analyzerMeta": { - "name": "find-exports", - "requiredAst": "oxc", - "identifier": "exporting-ref-project_1.0.0__-42206859", - "targetProject": { - "mainEntry": "./index.js", - "name": "exporting-ref-project", - "version": "1.0.0", - "commitHash": "[not-a-git-root]" - }, - "configuration": { - "skipFileImports": false, - "gatherFilesConfig": {}, - "skipCheckMatchCompatibility": false, - "addSystemPathsInResult": false - } - } - }, - "queryOutput": [ - { - "file": "./index.js", - "result": [ - { - "exportSpecifiers": [ - "[default]" - ], - "source": "./ref-src/core.js", - "normalizedSource": "./ref-src/core.js", - "rootFileMap": [ - { - "currentFileSpecifier": "[default]", - "rootFile": { - "file": "./ref-src/core.js", - "specifier": "[default]" - } - } - ] - }, - { - "exportSpecifiers": [ - "RefClass", - "RefRenamedClass" - ], - "localMap": [ - { - "local": "RefClass", - "exported": "RefRenamedClass" - } - ], - "source": "./ref-src/core.js", - "normalizedSource": "./ref-src/core.js", - "rootFileMap": [ - { - "currentFileSpecifier": "RefClass", - "rootFile": { - "file": "./ref-src/core.js", - "specifier": "RefClass" - } - }, - { - "currentFileSpecifier": "RefRenamedClass", - "rootFile": { - "file": "./ref-src/core.js", - "specifier": "RefClass" - } - } - ] - }, - { - "exportSpecifiers": [ - "[file]" - ], - "rootFileMap": [ - null - ] - } - ] - }, - { - "file": "./not-imported.js", - "result": [ - { - "exportSpecifiers": [ - "notImported" - ], - "localMap": [], - "rootFileMap": [ - { - "currentFileSpecifier": "notImported", - "rootFile": { - "file": "[current]", - "specifier": "notImported" - } - } - ] - }, - { - "exportSpecifiers": [ - "[file]" - ], - "rootFileMap": [ - null - ] - } - ] - }, - { - "file": "./ref-component.js", - "result": [ - { - "exportSpecifiers": [ - "[file]" - ], - "rootFileMap": [ - null - ] - } - ] - }, - { - "file": "./ref-src/core.js", - "result": [ - { - "exportSpecifiers": [ - "RefClass" - ], - "localMap": [], - "rootFileMap": [ - { - "currentFileSpecifier": "RefClass", - "rootFile": { - "file": "[current]", - "specifier": "RefClass" - } - } - ] - }, - { - "exportSpecifiers": [ - "[default]" - ], - "rootFileMap": [ - { - "currentFileSpecifier": "[default]", - "rootFile": { - "file": "[current]", - "specifier": "[default]" - } - } - ] - }, - { - "exportSpecifiers": [ - "[file]" - ], - "rootFileMap": [ - null - ] - } - ] - }, - { - "file": "./ref-src/folder/index.js", - "result": [ - { - "exportSpecifiers": [ - "resolvePathCorrect" - ], - "localMap": [], - "rootFileMap": [ - { - "currentFileSpecifier": "resolvePathCorrect", - "rootFile": { - "file": "[current]", - "specifier": "resolvePathCorrect" - } - } - ] - }, - { - "exportSpecifiers": [ - "[file]" - ], - "rootFileMap": [ - null - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-imports_-_importing-target-project_0.0.2-target-mock__349742630.json b/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-imports_-_importing-target-project_0.0.2-target-mock__349742630.json deleted file mode 100644 index f018952ba6..0000000000 --- a/packages-node/providence-analytics/test-node/program/analyzers/e2e/find-imports_-_importing-target-project_0.0.2-target-mock__349742630.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "meta": { - "searchType": "ast-analyzer", - "analyzerMeta": { - "name": "find-imports", - "requiredAst": "oxc", - "identifier": "importing-target-project_0.0.2-target-mock__349742630", - "targetProject": { - "mainEntry": "./target-src/match-imports/root-level-imports.js", - "name": "importing-target-project", - "version": "0.0.2-target-mock", - "commitHash": "[not-a-git-root]" - }, - "configuration": { - "keepInternalSources": false, - "gatherFilesConfig": {}, - "skipCheckMatchCompatibility": false, - "addSystemPathsInResult": false - } - } - }, - "queryOutput": [ - { - "file": "./target-src/find-customelements/multiple.js", - "result": [ - { - "importSpecifiers": [ - "RefClass" - ], - "source": "exporting-ref-project", - "normalizedSource": "exporting-ref-project" - } - ] - }, - { - "file": "./target-src/find-imports/all-notations.js", - "result": [ - { - "importSpecifiers": [ - "[file]" - ], - "source": "imported/source", - "normalizedSource": "imported/source" - }, - { - "importSpecifiers": [ - "[default]" - ], - "source": "imported/source-a", - "normalizedSource": "imported/source-a" - }, - { - "importSpecifiers": [ - "b" - ], - "source": "imported/source-b", - "normalizedSource": "imported/source-b" - }, - { - "importSpecifiers": [ - "c", - "d" - ], - "source": "imported/source-c", - "normalizedSource": "imported/source-c" - }, - { - "importSpecifiers": [ - "[default]", - "f", - "g" - ], - "source": "imported/source-d", - "normalizedSource": "imported/source-d" - }, - { - "importSpecifiers": [ - "[default]" - ], - "source": "my/source-e", - "normalizedSource": "my/source-e" - }, - { - "importSpecifiers": [ - "[default]" - ], - "source": "[variable]", - "normalizedSource": "[variable]" - }, - { - "importSpecifiers": [ - "[*]" - ], - "source": "imported/source-g", - "normalizedSource": "imported/source-g" - } - ] - }, - { - "file": "./target-src/match-imports/deep-imports.js", - "result": [ - { - "importSpecifiers": [ - "RefClass" - ], - "source": "exporting-ref-project/ref-src/core.js", - "normalizedSource": "exporting-ref-project/ref-src/core.js" - }, - { - "importSpecifiers": [ - "[default]" - ], - "source": "exporting-ref-project/ref-src/core.js", - "normalizedSource": "exporting-ref-project/ref-src/core.js" - }, - { - "importSpecifiers": [ - "nonMatched" - ], - "source": "unknown-project/xyz.js", - "normalizedSource": "unknown-project/xyz.js" - }, - { - "importSpecifiers": [ - "[file]" - ], - "source": "exporting-ref-project/ref-component", - "normalizedSource": "exporting-ref-project/ref-component" - }, - { - "importSpecifiers": [ - "resolvePathCorrect" - ], - "source": "exporting-ref-project/ref-src/folder", - "normalizedSource": "exporting-ref-project/ref-src/folder" - }, - { - "importSpecifiers": [ - "[*]" - ], - "source": "exporting-ref-project/ref-src/core.js", - "normalizedSource": "exporting-ref-project/ref-src/core.js" - } - ] - }, - { - "file": "./target-src/match-imports/root-level-imports.js", - "result": [ - { - "importSpecifiers": [ - "RefClass" - ], - "source": "exporting-ref-project", - "normalizedSource": "exporting-ref-project" - }, - { - "importSpecifiers": [ - "RefRenamedClass" - ], - "source": "exporting-ref-project", - "normalizedSource": "exporting-ref-project" - }, - { - "importSpecifiers": [ - "[default]" - ], - "source": "exporting-ref-project", - "normalizedSource": "exporting-ref-project" - }, - { - "importSpecifiers": [ - "nonMatched" - ], - "source": "unknown-project", - "normalizedSource": "unknown-project" - } - ] - }, - { - "file": "./target-src/match-subclasses/ExtendedComp.js", - "result": [ - { - "importSpecifiers": [ - "RefClass" - ], - "source": "exporting-ref-project", - "normalizedSource": "exporting-ref-project" - } - ] - }, - { - "file": "./target-src/match-subclasses/internalProxy.js", - "result": [ - { - "importSpecifiers": [ - "[default]" - ], - "source": "exporting-ref-project", - "normalizedSource": "exporting-ref-project" - } - ] - } - ] -} \ No newline at end of file diff --git a/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-imports_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209.json b/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-imports_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209.json deleted file mode 100644 index 8fc92eb6c0..0000000000 --- a/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-imports_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "meta": { - "searchType": "ast-analyzer", - "analyzerMeta": { - "name": "match-imports", - "requiredAst": "babel", - "identifier": "importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209", - "targetProject": { - "mainEntry": "./target-src/match-imports/root-level-imports.js", - "name": "importing-target-project", - "version": "0.0.2-target-mock", - "commitHash": "[not-a-git-root]" - }, - "referenceProject": { - "mainEntry": "./index.js", - "name": "exporting-ref-project", - "version": "1.0.0", - "commitHash": "[not-a-git-root]" - }, - "configuration": { - "gatherFilesConfig": {}, - "prefix": null, - "skipCheckMatchCompatibility": false, - "addSystemPathsInResult": false - } - } - }, - "queryOutput": [ - { - "name": "[default]", - "variable": { - "from": "[default]", - "to": "ExtendedComp", - "paths": [ - { - "from": "./index.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "./ref-src/core.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "exporting-ref-project/index.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "exporting-ref-project/ref-src/core.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - } - ] - } - }, - { - "name": "RefClass", - "variable": { - "from": "RefClass", - "to": "ExtendedComp", - "paths": [ - { - "from": "./index.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "./ref-src/core.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "exporting-ref-project/index.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "exporting-ref-project/ref-src/core.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - } - ] - }, - "tag": { - "from": "ref-component", - "to": "extended-comp", - "paths": [ - { - "from": "./ref-component.js", - "to": "./target-src/find-customelements/multiple.js" - }, - { - "from": "exporting-ref-project/ref-component.js", - "to": "./target-src/find-customelements/multiple.js" - } - ] - } - } - ] -} \ No newline at end of file diff --git a/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-imports_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1789378150.json b/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-imports_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1789378150.json deleted file mode 100644 index 9c18dde346..0000000000 --- a/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-imports_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1789378150.json +++ /dev/null @@ -1,162 +0,0 @@ -{ - "meta": { - "searchType": "ast-analyzer", - "analyzerMeta": { - "name": "match-imports", - "requiredAst": "oxc", - "identifier": "importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1789378150", - "targetProject": { - "mainEntry": "./target-src/match-imports/root-level-imports.js", - "name": "importing-target-project", - "version": "0.0.2-target-mock", - "commitHash": "[not-a-git-root]" - }, - "referenceProject": { - "mainEntry": "./index.js", - "name": "exporting-ref-project", - "version": "1.0.0", - "commitHash": "[not-a-git-root]" - }, - "configuration": { - "gatherFilesConfig": {}, - "targetProjectResult": null, - "referenceProjectResult": null, - "skipCheckMatchCompatibility": false, - "addSystemPathsInResult": false - } - } - }, - "queryOutput": [ - { - "exportSpecifier": { - "id": "[default]::./index.js::exporting-ref-project", - "name": "[default]", - "filePath": "./index.js", - "project": "exporting-ref-project" - }, - "matchesPerProject": [ - { - "project": "importing-target-project", - "files": [ - "./target-src/match-imports/root-level-imports.js", - "./target-src/match-subclasses/internalProxy.js" - ] - } - ] - }, - { - "exportSpecifier": { - "id": "RefClass::./index.js::exporting-ref-project", - "name": "RefClass", - "filePath": "./index.js", - "project": "exporting-ref-project" - }, - "matchesPerProject": [ - { - "project": "importing-target-project", - "files": [ - "./target-src/find-customelements/multiple.js", - "./target-src/match-imports/root-level-imports.js", - "./target-src/match-subclasses/ExtendedComp.js" - ] - } - ] - }, - { - "exportSpecifier": { - "id": "RefRenamedClass::./index.js::exporting-ref-project", - "name": "RefRenamedClass", - "filePath": "./index.js", - "project": "exporting-ref-project" - }, - "matchesPerProject": [ - { - "project": "importing-target-project", - "files": [ - "./target-src/match-imports/root-level-imports.js" - ] - } - ] - }, - { - "exportSpecifier": { - "id": "[file]::./ref-component.js::exporting-ref-project", - "name": "[file]", - "filePath": "./ref-component.js", - "project": "exporting-ref-project" - }, - "matchesPerProject": [ - { - "project": "importing-target-project", - "files": [ - "./target-src/match-imports/deep-imports.js" - ] - } - ] - }, - { - "exportSpecifier": { - "id": "RefClass::./ref-src/core.js::exporting-ref-project", - "name": "RefClass", - "filePath": "./ref-src/core.js", - "project": "exporting-ref-project" - }, - "matchesPerProject": [ - { - "project": "importing-target-project", - "files": [ - "./target-src/match-imports/deep-imports.js" - ] - } - ] - }, - { - "exportSpecifier": { - "id": "[default]::./ref-src/core.js::exporting-ref-project", - "name": "[default]", - "filePath": "./ref-src/core.js", - "project": "exporting-ref-project" - }, - "matchesPerProject": [ - { - "project": "importing-target-project", - "files": [ - "./target-src/match-imports/deep-imports.js" - ] - } - ] - }, - { - "exportSpecifier": { - "id": "[file]::./ref-src/core.js::exporting-ref-project", - "name": "[file]", - "filePath": "./ref-src/core.js", - "project": "exporting-ref-project" - }, - "matchesPerProject": [ - { - "project": "importing-target-project", - "files": [ - "./target-src/match-imports/deep-imports.js" - ] - } - ] - }, - { - "exportSpecifier": { - "id": "resolvePathCorrect::./ref-src/folder/index.js::exporting-ref-project", - "name": "resolvePathCorrect", - "filePath": "./ref-src/folder/index.js", - "project": "exporting-ref-project" - }, - "matchesPerProject": [ - { - "project": "importing-target-project", - "files": [ - "./target-src/match-imports/deep-imports.js" - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-paths_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209.json b/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-paths_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209.json deleted file mode 100644 index 274163089d..0000000000 --- a/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-paths_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "meta": { - "searchType": "ast-analyzer", - "analyzerMeta": { - "name": "match-paths", - "requiredAst": "babel", - "identifier": "importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209", - "targetProject": { - "mainEntry": "./target-src/match-imports/root-level-imports.js", - "name": "importing-target-project", - "version": "0.0.2-target-mock", - "commitHash": "[not-a-git-root]" - }, - "referenceProject": { - "mainEntry": "./index.js", - "name": "exporting-ref-project", - "version": "1.0.0", - "commitHash": "[not-a-git-root]" - }, - "configuration": { - "gatherFilesConfig": {}, - "prefix": null, - "skipCheckMatchCompatibility": false, - "addSystemPathsInResult": false - } - } - }, - "queryOutput": [ - { - "name": "[default]", - "variable": { - "from": "[default]", - "to": "ExtendedComp", - "paths": [ - { - "from": "./index.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "./ref-src/core.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "exporting-ref-project/index.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "exporting-ref-project/ref-src/core.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - } - ] - } - }, - { - "name": "RefClass", - "variable": { - "from": "RefClass", - "to": "ExtendedComp", - "paths": [ - { - "from": "./index.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "./ref-src/core.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "exporting-ref-project/index.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - }, - { - "from": "exporting-ref-project/ref-src/core.js", - "to": "./target-src/match-subclasses/ExtendedComp.js" - } - ] - }, - "tag": { - "from": "ref-component", - "to": "extended-comp", - "paths": [ - { - "from": "./ref-component.js", - "to": "./target-src/find-customelements/multiple.js" - }, - { - "from": "exporting-ref-project/ref-component.js", - "to": "./target-src/find-customelements/multiple.js" - } - ] - } - } - ] -} \ No newline at end of file diff --git a/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-subclasses_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1982316146.json b/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-subclasses_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1982316146.json deleted file mode 100644 index e85c53c6c0..0000000000 --- a/packages-node/providence-analytics/test-node/program/analyzers/e2e/match-subclasses_-_importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1982316146.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "meta": { - "searchType": "ast-analyzer", - "analyzerMeta": { - "name": "match-subclasses", - "requiredAst": "oxc", - "identifier": "importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1982316146", - "targetProject": { - "mainEntry": "./target-src/match-imports/root-level-imports.js", - "name": "importing-target-project", - "version": "0.0.2-target-mock", - "commitHash": "[not-a-git-root]" - }, - "referenceProject": { - "mainEntry": "./index.js", - "name": "exporting-ref-project", - "version": "1.0.0", - "commitHash": "[not-a-git-root]" - }, - "configuration": { - "gatherFilesConfig": {}, - "skipCheckMatchCompatibility": false, - "addSystemPathsInResult": false - } - } - }, - "queryOutput": [ - { - "exportSpecifier": { - "name": "[default]", - "project": "exporting-ref-project", - "filePath": "./index.js", - "id": "[default]::./index.js::exporting-ref-project" - }, - "matchesPerProject": [ - { - "project": "importing-target-project", - "files": [ - { - "file": "./target-src/match-subclasses/ExtendedComp.js", - "identifier": "ExtendedComp" - } - ] - } - ] - }, - { - "exportSpecifier": { - "name": "RefClass", - "project": "exporting-ref-project", - "filePath": "./index.js", - "id": "RefClass::./index.js::exporting-ref-project" - }, - "matchesPerProject": [ - { - "project": "importing-target-project", - "files": [ - { - "file": "./target-src/match-subclasses/ExtendedComp.js", - "identifier": "ExtendedComp" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/packages-node/providence-analytics/test-node/program/analyzers/find-classes.test.js b/packages-node/providence-analytics/test-node/program/analyzers/find-classes.test.js index 2501ca0839..1ebf26f49c 100644 --- a/packages-node/providence-analytics/test-node/program/analyzers/find-classes.test.js +++ b/packages-node/providence-analytics/test-node/program/analyzers/find-classes.test.js @@ -12,174 +12,277 @@ import FindClassesAnalyzer from '../../../src/program/analyzers/find-classes.js' setupAnalyzerTest(); -describe('Analyzer "find-classes"', async () => { - const findClassesQueryConfig = await QueryService.getQueryConfigFromAnalyzer(FindClassesAnalyzer); - - // const queryResults = setupAnalyzerTest(); - /** @type {Partial} */ - const _providenceCfg = { - targetProjectPaths: ['/fictional/project'], // defined in mockProject - }; - - it(`finds class definitions`, async () => { - mockProject([`class EmptyClass {}`]); - const queryResults = await providence(findClassesQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result).to.deep.equal([ - { - name: 'EmptyClass', - isMixin: false, - members: { - methods: [], - props: [], +/** + * @param {{parser: 'oxc'|'swc'}} opts + */ +function runFindClassesSuite({ parser }) { + describe('Analyzer "find-classes"', async () => { + const findClassesQueryConfig = + await QueryService.getQueryConfigFromAnalyzer(FindClassesAnalyzer); + + // const queryResults = setupAnalyzerTest(); + /** @type {Partial} */ + const _providenceCfg = { + targetProjectPaths: ['/fictional/project'], // defined in mockProject + parser, + }; + + it(`finds class definitions`, async () => { + mockProject([`class EmptyClass {}`]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result).to.deep.equal([ + { + name: 'EmptyClass', + isMixin: false, + members: { + methods: [], + props: [], + }, }, - }, - ]); - }); + ]); + }); - it(`finds mixin definitions`, async () => { - mockProject([`const m = superclass => class MyMixin extends superclass {}`]); - const queryResults = await providence(findClassesQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result).to.deep.equal([ - { - name: 'MyMixin', - superClasses: [ - { - isMixin: false, - name: 'superclass', - rootFile: { file: '[current]', specifier: 'superclass' }, + it(`finds mixin definitions`, async () => { + mockProject([`const m = superclass => class MyMixin extends superclass {}`]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result).to.deep.equal([ + { + name: 'MyMixin', + superClasses: [ + { + isMixin: false, + name: 'superclass', + rootFile: { file: '[current]', specifier: 'superclass' }, + }, + ], + isMixin: true, + members: { + methods: [], + props: [], }, - ], - isMixin: true, - members: { - methods: [], - props: [], }, - }, - ]); - }); + ]); + }); - it(`stores superClasses`, async () => { - mockProject({ - './index.js': ` + it(`stores superClasses`, async () => { + mockProject({ + './index.js': ` import { Mixin } from '@external/source'; class OtherClass {} export class EmptyClass extends Mixin(OtherClass) {} `, - './internal.js': '', + './internal.js': '', + }); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[1].superClasses).to.deep.equal([ + { + isMixin: true, + name: 'Mixin', + rootFile: { file: '@external/source', specifier: 'Mixin' }, + }, + { + isMixin: false, + name: 'OtherClass', + rootFile: { file: '[current]', specifier: 'OtherClass' }, + }, + ]); }); - const queryResults = await providence(findClassesQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[1].superClasses).to.deep.equal([ - { - isMixin: true, - name: 'Mixin', - rootFile: { file: '@external/source', specifier: 'Mixin' }, - }, - { - isMixin: false, - name: 'OtherClass', - rootFile: { file: '[current]', specifier: 'OtherClass' }, - }, - ]); - }); - it(`handles multiple classes per file`, async () => { - mockProject([ - ` const m = superclass => class MyMixin extends superclass {} + it(`handles multiple classes per file`, async () => { + mockProject([ + ` const m = superclass => class MyMixin extends superclass {} class EmptyClass extends Mixin(OtherClass) {}`, - ]); - const queryResults = await providence(findClassesQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result.length).to.equal(2); - }); + ]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result.length).to.equal(2); + }); - describe('Members', () => { - it(`stores methods`, async () => { - mockProject([ - `class MyClass { + it(`handles nameless classes`, async () => { + mockProject([`const x = class extends HTMLElement {};`]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + + expect(firstEntry.result[0]).to.deep.equal({ + name: null, + isMixin: true, + superClasses: [ + { + name: 'HTMLElement', + isMixin: false, + rootFile: { + file: '[current]', + specifier: 'HTMLElement', + }, + }, + ], + members: { props: [], methods: [] }, + }); + }); + + describe('Edge cases', () => { + it(`handles customElements.get retrieved from registry correctly`, async () => { + mockProject([`class ExtendedOnTheFly extends customElements.get('on-the-fly') {}`]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + + expect(firstEntry.result[0].superClasses).to.deep.equal([ + { + rootFile: { file: '[current]', specifier: null }, + customElementsGetRef: 'on-the-fly', + name: null, + isMixin: false, + }, + ]); + }); + + it(`handles window.customElements.get retrieved from registry correctly`, async () => { + mockProject([`class ExtendedOnTheFly extends window.customElements.get('on-the-fly') {}`]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + + expect(firstEntry.result[0].superClasses).to.deep.equal([ + { + rootFile: { file: '[current]', specifier: null }, + customElementsGetRef: 'on-the-fly', + name: null, + isMixin: false, + }, + ]); + }); + + it(`handles definitions inside ce definitions`, async () => { + mockProject([`customElements.define('on-the-fly', class extends HTMLElement {});`]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + + expect(firstEntry.result[0]).to.deep.equal({ + name: null, + // TODO: fix + isMixin: true, + superClasses: [ + { + name: 'HTMLElement', + isMixin: false, + rootFile: { + file: '[current]', + specifier: 'HTMLElement', + }, + }, + ], + members: { + props: [], + methods: [], + }, + }); + }); + }); + + describe('Members', () => { + it(`stores methods`, async () => { + mockProject([ + `class MyClass { method() {} _protectedMethod() {} __privateMethod() {} $protectedMethod() {} $$privateMethod() {} }`, - ]); - const queryResults = await providence(findClassesQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].members.methods).to.deep.equal([ - { - accessType: 'public', - name: 'method', - }, - { - accessType: 'protected', - name: '_protectedMethod', - }, - { - accessType: 'private', - name: '__privateMethod', - }, - { - accessType: 'protected', - name: '$protectedMethod', - }, - { - accessType: 'private', - name: '$$privateMethod', - }, - ]); - }); + ]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].members.methods).to.deep.equal([ + { + accessType: 'public', + name: 'method', + }, + { + accessType: 'protected', + name: '_protectedMethod', + }, + { + accessType: 'private', + name: '__privateMethod', + }, + { + accessType: 'protected', + name: '$protectedMethod', + }, + { + accessType: 'private', + name: '$$privateMethod', + }, + ]); + }); - it(`stores props`, async () => { - mockProject([ - `class MyClass { + it(`stores props`, async () => { + mockProject([ + `class MyClass { get getterSetter() {} set getterSetter(v) {} static get _staticGetterSetter() {} static set _staticGetterSetter(v) {} }`, - ]); - const queryResults = await providence(findClassesQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].members.props).to.deep.equal([ - { - accessType: 'public', - kind: ['get', 'set'], - name: 'getterSetter', - }, - { - accessType: 'protected', - kind: ['get', 'set'], - name: '_staticGetterSetter', - static: true, - }, - ]); - }); + ]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].members.props).to.deep.equal([ + { + accessType: 'public', + kind: ['get', 'set'], + name: 'getterSetter', + }, + { + accessType: 'protected', + kind: ['get', 'set'], + name: '_staticGetterSetter', + static: true, + }, + ]); + }); - // Options below are disabled by default for now. - // TODO: provide as options - it.skip(`filters out platform members`, async () => { - mockProject([ - `class MyClass { + it(`handles constructor as method as well`, async () => { + mockProject([ + `class MyClass { + constructor() {} + }`, + ]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].members.methods).to.deep.equal([ + { + accessType: '[n/a]', + name: 'constructor', + isPartOfPlatformLifeCycle: true, + }, + ]); + }); + + // Options below are disabled by default for now. + // TODO: provide as options + it.skip(`filters out platform members`, async () => { + mockProject([ + `class MyClass { static get attributes() {} constructor() {} connectedCallback() {} disconnectedCallback() {} }`, - ]); - const queryResults = await providence(findClassesQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].members.methods.length).to.equal(0); - expect(firstEntry.result[0].members.props.length).to.equal(0); - }); + ]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].members.methods.length).to.equal(0); + expect(firstEntry.result[0].members.props.length).to.equal(0); + }); - it.skip(`filters out LitElement members`, async () => { - mockProject([ - `class MyClass { + it.skip(`filters out LitElement members`, async () => { + mockProject([ + `class MyClass { static get properties() {} static get styles() {} get updateComplete() {} @@ -191,25 +294,29 @@ describe('Analyzer "find-classes"', async () => { update() {} shouldUpdate() {} }`, - ]); - const queryResults = await providence(findClassesQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].members.methods.length).to.equal(0); - expect(firstEntry.result[0].members.props.length).to.equal(0); - }); + ]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].members.methods.length).to.equal(0); + expect(firstEntry.result[0].members.props.length).to.equal(0); + }); - it.skip(`filters out Lion members`, async () => { - mockProject([ - `class MyClass { + it.skip(`filters out Lion members`, async () => { + mockProject([ + `class MyClass { static get localizeNamespaces() {} get slots() {} onLocaleUpdated() {} }`, - ]); - const queryResults = await providence(findClassesQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].members.methods.length).to.equal(0); - expect(firstEntry.result[0].members.props.length).to.equal(0); + ]); + const queryResults = await providence(findClassesQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].members.methods.length).to.equal(0); + expect(firstEntry.result[0].members.props.length).to.equal(0); + }); }); }); -}); +} + +runFindClassesSuite({ parser: 'oxc' }); +runFindClassesSuite({ parser: 'swc' }); diff --git a/packages-node/providence-analytics/test-node/program/analyzers/find-customelements.test.js b/packages-node/providence-analytics/test-node/program/analyzers/find-customelements.test.js index 0a1db55464..5dae541af0 100644 --- a/packages-node/providence-analytics/test-node/program/analyzers/find-customelements.test.js +++ b/packages-node/providence-analytics/test-node/program/analyzers/find-customelements.test.js @@ -12,105 +12,113 @@ import FindCustomelementsAnalyzer from '../../../src/program/analyzers/find-cust setupAnalyzerTest(); -describe('Analyzer "find-customelements"', async () => { - const findCustomelementsQueryConfig = await QueryService.getQueryConfigFromAnalyzer( - FindCustomelementsAnalyzer, - ); - /** @type {Partial} */ - const _providenceCfg = { - targetProjectPaths: ['/fictional/project'], // defined in mockProject - }; +/** + * @param {{parser: 'oxc'|'swc'}} opts + */ +function runFindCustomelementsSuite({ parser }) { + describe('Analyzer "find-customelements"', async () => { + const findCustomelementsQueryConfig = await QueryService.getQueryConfigFromAnalyzer( + FindCustomelementsAnalyzer, + ); + /** @type {Partial} */ + const _providenceCfg = { + targetProjectPaths: ['/fictional/project'], // defined in mockProject + parser, + }; - it(`stores the tagName of a custom element`, async () => { - mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]); - const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].tagName).to.equal('custom-el'); - }); + it(`stores the tagName of a custom element`, async () => { + mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]); + const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].tagName).to.equal('custom-el'); + }); - it(`allows different notations for defining a custom element`, async () => { - mockProject([ - `customElements.define('custom-el1', class extends HTMLElement {});`, - `window.customElements.define('custom-el2', class extends HTMLElement {});`, - `(() => { + it(`allows different notations for defining a custom element`, async () => { + mockProject([ + `customElements.define('custom-el1', class extends HTMLElement {});`, + `window.customElements.define('custom-el2', class extends HTMLElement {});`, + `(() => { window.customElements.define('custom-el3', class extends HTMLElement {}); })();`, - ]); - const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - const secondEntry = getEntry(queryResult, 1); - const thirdEntry = getEntry(queryResult, 2); - expect(firstEntry.result[0].tagName).to.equal('custom-el1'); - expect(secondEntry.result[0].tagName).to.equal('custom-el2'); - expect(thirdEntry.result[0].tagName).to.equal('custom-el3'); - }); + ]); + const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + const secondEntry = getEntry(queryResult, 1); + const thirdEntry = getEntry(queryResult, 2); + expect(firstEntry.result[0].tagName).to.equal('custom-el1'); + expect(secondEntry.result[0].tagName).to.equal('custom-el2'); + expect(thirdEntry.result[0].tagName).to.equal('custom-el3'); + }); - it(`stores the rootFile of a custom element`, async () => { - mockProject({ - './src/CustomEl.js': `export class CustomEl extends HTMLElement {}`, - './custom-el.js': ` + it(`stores the rootFile of a custom element`, async () => { + mockProject({ + './src/CustomEl.js': `export class CustomEl extends HTMLElement {}`, + './custom-el.js': ` import { CustomEl } from './src/CustomEl.js'; customElements.define('custom-el', CustomEl); `, + }); + const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].rootFile).to.deep.equal({ + file: './src/CustomEl.js', + specifier: 'CustomEl', + }); }); - const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].rootFile).to.deep.equal({ - file: './src/CustomEl.js', - specifier: 'CustomEl', - }); - }); - it(`stores "[inline]" constructors`, async () => { - mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]); - const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].constructorIdentifier).to.equal('[inline]'); - expect(firstEntry.result[0].rootFile.specifier).to.equal('[inline]'); - }); + it(`stores "[inline]" constructors`, async () => { + mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]); + const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].constructorIdentifier).to.equal('[inline]'); + expect(firstEntry.result[0].rootFile.specifier).to.equal('[inline]'); + }); - it(`stores "[current]" rootFile`, async () => { - mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]); - const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].rootFile.file).to.equal('[current]'); - }); + it(`stores "[current]" rootFile`, async () => { + mockProject([`customElements.define('custom-el', class extends HTMLElement {});`]); + const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].rootFile.file).to.equal('[current]'); + }); - it(`stores the locally exported specifier in the rootFile `, async () => { - mockProject({ - './src/CustomEl.js': `export class CustomEl extends HTMLElement {}`, - './custom-el.js': ` + it(`stores the locally exported specifier in the rootFile `, async () => { + mockProject({ + './src/CustomEl.js': `export class CustomEl extends HTMLElement {}`, + './custom-el.js': ` import { CustomEl } from './src/CustomEl.js'; customElements.define('custom-el', CustomEl); `, + }); + const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].constructorIdentifier).to.equal('CustomEl'); + expect(firstEntry.result[0].rootFile.specifier).to.equal('CustomEl'); }); - const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].constructorIdentifier).to.equal('CustomEl'); - expect(firstEntry.result[0].rootFile.specifier).to.equal('CustomEl'); - }); - it(`finds all occurrences of custom elements`, async () => { - mockProject([ - ` + it(`finds all occurrences of custom elements`, async () => { + mockProject([ + ` customElements.define('tag-1', class extends HTMLElement {}); customElements.define('tag-2', class extends HTMLElement {}); `, - ` + ` customElements.define('tag-3', class extends HTMLElement {}); `, - ]); - const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - const secondEntry = getEntry(queryResult, 1); - expect(firstEntry.result.length).to.equal(2); - expect(secondEntry.result.length).to.equal(1); + ]); + const queryResults = await providence(findCustomelementsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + const secondEntry = getEntry(queryResult, 1); + expect(firstEntry.result.length).to.equal(2); + expect(secondEntry.result.length).to.equal(1); + }); }); -}); +} +runFindCustomelementsSuite({ parser: 'oxc' }); +runFindCustomelementsSuite({ parser: 'swc' }); diff --git a/packages-node/providence-analytics/test-node/program/analyzers/find-exports.test.js b/packages-node/providence-analytics/test-node/program/analyzers/find-exports.test.js index dd9e9bf760..09e2c095e3 100644 --- a/packages-node/providence-analytics/test-node/program/analyzers/find-exports.test.js +++ b/packages-node/providence-analytics/test-node/program/analyzers/find-exports.test.js @@ -12,347 +12,357 @@ import FindExportsAnalyzer from '../../../src/program/analyzers/find-exports.js' setupAnalyzerTest(); -describe('Analyzer "find-exports"', async () => { - const findExportsQueryConfig = await QueryService.getQueryConfigFromAnalyzer(FindExportsAnalyzer); - - /** @type {Partial} */ - const _providenceCfg = { - targetProjectPaths: ['/fictional/project'], // defined in mockProject - }; - - describe('Export notations', () => { - it(`supports "export const x = 0;" (named specifier)`, async () => { - mockProject([`export const x = 0`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstResult = getEntry(queryResults[0]).result[0]; - - expect(firstResult.exportSpecifiers).to.deep.equal(['x']); - expect(firstResult.source).to.be.undefined; - }); - - it(`supports "export default class X {};" (default export)`, async () => { - mockProject([`export default class X {}`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstResult = getEntry(queryResults[0]).result[0]; - expect(firstResult.exportSpecifiers).to.deep.equal(['[default]']); - expect(firstResult.source).to.be.undefined; - }); +/** + * @param {{parser: 'oxc'|'swc'}} opts + */ +function runFindExportsSuite({ parser }) { + describe('Analyzer "find-exports"', async () => { + const findExportsQueryConfig = + await QueryService.getQueryConfigFromAnalyzer(FindExportsAnalyzer); + + /** @type {Partial} */ + const _providenceCfg = { + targetProjectPaths: ['/fictional/project'], // defined in mockProject + parser, + }; + + describe('Export notations', () => { + it(`supports "export const x = 0;" (named specifier)`, async () => { + mockProject([`export const x = 0`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstResult = getEntry(queryResults[0]).result[0]; + + expect(firstResult.exportSpecifiers).to.deep.equal(['x']); + expect(firstResult.source).to.be.undefined; + }); - it(`supports "export default x => x * 3;" (default function export)`, async () => { - mockProject([`export default x => x * 3`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstResult = getEntry(queryResults[0]).result[0]; + it(`supports "export default class X {};" (default export)`, async () => { + mockProject([`export default class X {}`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstResult = getEntry(queryResults[0]).result[0]; + expect(firstResult.exportSpecifiers).to.deep.equal(['[default]']); + expect(firstResult.source).to.be.undefined; + }); - expect(firstResult.exportSpecifiers).to.deep.equal(['[default]']); - expect(firstResult.source).to.be.undefined; - }); + it(`supports "export default x => x * 3;" (default function export)`, async () => { + mockProject([`export default x => x * 3`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstResult = getEntry(queryResults[0]).result[0]; - it(`supports "export {default as x} from 'y';" (default re-export)`, async () => { - mockProject({ - './file-with-default-export.js': 'export default 1;', - './file-with-default-re-export.js': - "export { default as namedExport } from './file-with-default-export.js';", + expect(firstResult.exportSpecifiers).to.deep.equal(['[default]']); + expect(firstResult.source).to.be.undefined; }); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstResult = getEntry(queryResults[0]).result[0]; - expect(firstResult).to.deep.equal({ - exportSpecifiers: ['[default]'], - source: undefined, - rootFileMap: [ - { - currentFileSpecifier: '[default]', - rootFile: { file: '[current]', specifier: '[default]' }, - }, - ], + it(`supports "export {default as x} from 'y';" (default re-export)`, async () => { + mockProject({ + './file-with-default-export.js': 'export default 1;', + './file-with-default-re-export.js': + "export { default as namedExport } from './file-with-default-export.js';", + }); + + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstResult = getEntry(queryResults[0]).result[0]; + expect(firstResult).to.deep.equal({ + exportSpecifiers: ['[default]'], + source: undefined, + rootFileMap: [ + { + currentFileSpecifier: '[default]', + rootFile: { file: '[current]', specifier: '[default]' }, + }, + ], + }); + + const secondEntry = getEntry(queryResults[0], 1); + expect(secondEntry.result[0]).to.deep.equal({ + exportSpecifiers: ['namedExport'], + source: './file-with-default-export.js', + localMap: [{ exported: 'namedExport', local: '[default]' }], + normalizedSource: './file-with-default-export.js', + rootFileMap: [ + { + currentFileSpecifier: 'namedExport', + rootFile: { file: './file-with-default-export.js', specifier: '[default]' }, + }, + ], + }); }); - const secondEntry = getEntry(queryResults[0], 1); - expect(secondEntry.result[0]).to.deep.equal({ - exportSpecifiers: ['namedExport'], - source: './file-with-default-export.js', - localMap: [{ exported: 'namedExport', local: '[default]' }], - normalizedSource: './file-with-default-export.js', - rootFileMap: [ - { - currentFileSpecifier: 'namedExport', - rootFile: { file: './file-with-default-export.js', specifier: '[default]' }, - }, - ], + it(`supports "import {x} from 'y'; export default x;" (named re-export as default)`, async () => { + mockProject([`import {x} from 'y'; export default x;`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal('y'); }); - }); - - it(`supports "import {x} from 'y'; export default x;" (named re-export as default)`, async () => { - mockProject([`import {x} from 'y'; export default x;`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); - expect(firstEntry.result[0].source).to.equal('y'); - }); - - it(`supports "import x from 'y'; export default x" (default re-export as default)`, async () => { - mockProject([`import x from 'y'; export default x;`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); - expect(firstEntry.result[0].source).to.equal('y'); - }); - - it(`supports "const x = 0; export default x;" (default, referenced export)`, async () => { - mockProject([`const x = 0; export default x;`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); - expect(firstEntry.result[0].source).to.equal(undefined); - }); - it(`supports "class X {}; export default X;" (default, referenced ClassDeclaration export)`, async () => { - mockProject([`class X {}; export default X;`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); - expect(firstEntry.result[0].source).to.equal(undefined); - }); - - it(`supports "function x() {}; export default x;" (default, referenced FunctionDeclaration export)`, async () => { - mockProject([`function x() {}; export default x;`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); - expect(firstEntry.result[0].source).to.equal(undefined); - }); + it(`supports "import x from 'y'; export default x" (default re-export as default)`, async () => { + mockProject([`import x from 'y'; export default x;`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal('y'); + }); - it(`supports "export { x } from 'my/source'" (re-export named specifier)`, async () => { - mockProject([`export { x } from 'my/source'`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('x'); - expect(firstEntry.result[0].source).to.equal('my/source'); - }); + it(`supports "const x = 0; export default x;" (default, referenced export)`, async () => { + mockProject([`const x = 0; export default x;`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal(undefined); + }); - it(`supports [export { x as y } from 'my/source'] (re-export renamed specifier)`, async () => { - mockProject([`export { x as y } from 'my/source'`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('y'); - expect(firstEntry.result[0].source).to.equal('my/source'); - }); + it(`supports "class X {}; export default X;" (default, referenced ClassDeclaration export)`, async () => { + mockProject([`class X {}; export default X;`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal(undefined); + }); - it(`supports [export styles from './styles.css' assert { type: "css" }] (import assertions)`, async () => { - mockProject({ - './styles.css': '.block { display:block; };', - './x.js': `export { styles as default } from './styles.css' assert { type: "css" };`, + it(`supports "function x() {}; export default x;" (default, referenced FunctionDeclaration export)`, async () => { + mockProject([`function x() {}; export default x;`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal(undefined); }); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); - expect(firstEntry.result[0].source).to.equal('./styles.css'); - expect(firstEntry.result[0].rootFileMap[0]).to.deep.equal({ - currentFileSpecifier: '[default]', - rootFile: { - file: './styles.css', - specifier: '[default]', - }, + + it(`supports "export { x } from 'my/source'" (re-export named specifier)`, async () => { + mockProject([`export { x } from 'my/source'`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('x'); + expect(firstEntry.result[0].source).to.equal('my/source'); }); - }); - it(`supports [import styles from './styles.css' assert { type: "css" }; export default styles;] (import assertions)`, async () => { - mockProject({ - './styles.css': '.block { display:block; };', - './x.js': `import styles from './styles.css' assert { type: "css" }; export default styles;`, + it(`supports [export { x as y } from 'my/source'] (re-export renamed specifier)`, async () => { + mockProject([`export { x as y } from 'my/source'`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('y'); + expect(firstEntry.result[0].source).to.equal('my/source'); }); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); - expect(firstEntry.result[0].source).to.equal('./styles.css'); - expect(firstEntry.result[0].rootFileMap[0]).to.deep.equal({ - currentFileSpecifier: '[default]', - rootFile: { - file: './styles.css', - specifier: '[default]', - }, + + it(`supports [export styles from './styles.css' assert { type: "css" }] (import assertions)`, async () => { + mockProject({ + './styles.css': '.block { display:block; };', + './x.js': `export { styles as default } from './styles.css' assert { type: "css" };`, + }); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal('./styles.css'); + expect(firstEntry.result[0].rootFileMap[0]).to.deep.equal({ + currentFileSpecifier: '[default]', + rootFile: { + file: './styles.css', + specifier: '[default]', + }, + }); }); - }); - it(`stores meta info(local name) of renamed specifiers`, async () => { - mockProject([`export { x as y } from 'my/source'`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - // This info will be relevant later to identify 'transitive' relations - expect(firstEntry.result[0].localMap).to.deep.equal([ - { - local: 'x', - exported: 'y', - }, - ]); - }); + it(`supports [import styles from './styles.css' assert { type: "css" }; export default styles;] (import assertions)`, async () => { + mockProject({ + './styles.css': '.block { display:block; };', + './x.js': `import styles from './styles.css' assert { type: "css" }; export default styles;`, + }); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal('./styles.css'); + expect(firstEntry.result[0].rootFileMap[0]).to.deep.equal({ + currentFileSpecifier: '[default]', + rootFile: { + file: './styles.css', + specifier: '[default]', + }, + }); + }); - it(`supports "export { x, y } from 'my/source';" (multiple re-exported named specifiers)`, async () => { - mockProject([`export { x, y } from 'my/source'`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(2); - expect(firstEntry.result[0].exportSpecifiers).to.deep.equal(['x', 'y']); - expect(firstEntry.result[0].source).to.equal('my/source'); - }); + it(`stores meta info(local name) of renamed specifiers`, async () => { + mockProject([`export { x as y } from 'my/source'`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + // This info will be relevant later to identify 'transitive' relations + expect(firstEntry.result[0].localMap).to.deep.equal([ + { + local: 'x', + exported: 'y', + }, + ]); + }); - it(`stores rootFileMap of an exported Identifier`, async () => { - 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'`, + it(`supports "export { x, y } from 'my/source';" (multiple re-exported named specifiers)`, async () => { + mockProject([`export { x, y } from 'my/source'`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(2); + expect(firstEntry.result[0].exportSpecifiers).to.deep.equal(['x', 'y']); + expect(firstEntry.result[0].source).to.equal('my/source'); }); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - const secondEntry = getEntry(queryResults[0], 1); - const thirdEntry = getEntry(queryResults[0], 2); + it(`stores rootFileMap of an exported Identifier`, async () => { + 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'`, + }); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - expect(firstEntry.result[0].rootFileMap).to.deep.equal([ - { - currentFileSpecifier: 'MyComp', // this is the local name in the file we track from - rootFile: { - file: './src/OriginalComp.js', // the file containing declaration - specifier: 'OriginalComp', // the specifier that was exported in file + const firstEntry = getEntry(queryResults[0]); + const secondEntry = getEntry(queryResults[0], 1); + const thirdEntry = getEntry(queryResults[0], 2); + + expect(firstEntry.result[0].rootFileMap).to.deep.equal([ + { + currentFileSpecifier: 'MyComp', // this is the local name in the file we track from + rootFile: { + file: './src/OriginalComp.js', // the file containing declaration + specifier: 'OriginalComp', // the specifier that was exported in file + }, }, - }, - ]); - expect(secondEntry.result[0].rootFileMap).to.deep.equal([ - { - currentFileSpecifier: 'InBetweenComp', - rootFile: { - file: './src/OriginalComp.js', - specifier: 'OriginalComp', + ]); + expect(secondEntry.result[0].rootFileMap).to.deep.equal([ + { + currentFileSpecifier: 'InBetweenComp', + rootFile: { + file: './src/OriginalComp.js', + specifier: 'OriginalComp', + }, }, - }, - ]); - expect(thirdEntry.result[0].rootFileMap).to.deep.equal([ - { - currentFileSpecifier: 'OriginalComp', - rootFile: { - file: '[current]', - specifier: 'OriginalComp', + ]); + expect(thirdEntry.result[0].rootFileMap).to.deep.equal([ + { + currentFileSpecifier: 'OriginalComp', + rootFile: { + file: '[current]', + specifier: 'OriginalComp', + }, }, - }, - ]); - }); + ]); + }); - it(`stores rootFileMap of an exported Identifier`, async () => { - mockProject({ - './src/reexport.js': ` + it(`stores rootFileMap of an exported Identifier`, async () => { + mockProject({ + './src/reexport.js': ` // a direct default import import RefDefault from 'exporting-ref-project'; export default RefDefault; `, - './index.js': ` + './index.js': ` import ExtendRefDefault from './src/reexport.js'; export default ExtendRefDefault; `, - }); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); + }); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].rootFileMap).to.deep.equal([ - { - currentFileSpecifier: '[default]', - rootFile: { - file: 'exporting-ref-project', - specifier: '[default]', + expect(firstEntry.result[0].rootFileMap).to.deep.equal([ + { + currentFileSpecifier: '[default]', + rootFile: { + file: 'exporting-ref-project', + specifier: '[default]', + }, }, - }, - ]); - }); + ]); + }); - it(`correctly handles empty files`, async () => { - // These can be encountered while scanning repos.. They should not break the code... - mockProject([`// some comment here...`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers).to.deep.equal(['[file]']); - expect(firstEntry.result[0].source).to.equal(undefined); + it(`correctly handles empty files`, async () => { + // These can be encountered while scanning repos.. They should not break the code... + mockProject([`// some comment here...`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers).to.deep.equal(['[file]']); + expect(firstEntry.result[0].source).to.equal(undefined); + }); }); - }); - describe('Export variable types', () => { - it(`classes`, async () => { - mockProject([`export class X {}`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('X'); - expect(firstEntry.result[0].source).to.be.undefined; - }); + describe('Export variable types', () => { + it(`classes`, async () => { + mockProject([`export class X {}`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('X'); + expect(firstEntry.result[0].source).to.be.undefined; + }); - it(`functions`, async () => { - mockProject([`export function y() {}`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('y'); - expect(firstEntry.result[0].source).to.be.undefined; - }); + it(`functions`, async () => { + mockProject([`export function y() {}`]); + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const firstEntry = getEntry(queryResults[0]); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('y'); + expect(firstEntry.result[0].source).to.be.undefined; + }); - // ...etc? - // ...TODO: create custom hooks to store meta info about types etc. - }); + // ...etc? + // ...TODO: create custom hooks to store meta info about types etc. + }); - describe('Default post processing', () => { - // onlyInternalSources: false, - // keepOriginalSourcePaths: false, - // filterSpecifier: null, - }); + describe('Default post processing', () => { + // onlyInternalSources: false, + // keepOriginalSourcePaths: false, + // filterSpecifier: null, + }); - describe('Options', () => { - // TODO: Move to dashboard - it.skip(`"metaConfig.categoryConfig"`, async () => { - mockProject( - [ - `export const foo = null`, // firstEntry - `export const bar = null`, // secondEntry - `export const baz = null`, // thirdEntry - ], - { - projectName: 'my-project', - filePaths: ['./foo.js', './packages/bar/test/bar.test.js', './temp/baz.js'], - }, - ); - - // const findExportsCategoryQueryObj = await QueryService.getQueryConfigFromAnalyzer( - // 'find-exports', - // { - // metaConfig: { - // categoryConfig: [ - // { - // project: 'my-project', - // categories: { - // fooCategory: localFilePath => localFilePath.startsWith('./foo'), - // barCategory: localFilePath => localFilePath.startsWith('./packages/bar'), - // testCategory: localFilePath => localFilePath.includes('/test/'), - // }, - // }, - // ], - // }, - // }, - // ); - - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const [firstEntry, secondEntry, thirdEntry] = getEntries(queryResult); - expect(firstEntry.meta.categories).to.deep.equal(['fooCategory']); - // not mutually exclusive... - expect(secondEntry.meta.categories).to.deep.equal(['barCategory', 'testCategory']); - expect(thirdEntry.meta.categories).to.deep.equal([]); + describe('Options', () => { + // TODO: Move to dashboard + it.skip(`"metaConfig.categoryConfig"`, async () => { + mockProject( + [ + `export const foo = null`, // firstEntry + `export const bar = null`, // secondEntry + `export const baz = null`, // thirdEntry + ], + { + projectName: 'my-project', + filePaths: ['./foo.js', './packages/bar/test/bar.test.js', './temp/baz.js'], + }, + ); + + // const findExportsCategoryQueryObj = await QueryService.getQueryConfigFromAnalyzer( + // 'find-exports', + // { + // metaConfig: { + // categoryConfig: [ + // { + // project: 'my-project', + // categories: { + // fooCategory: localFilePath => localFilePath.startsWith('./foo'), + // barCategory: localFilePath => localFilePath.startsWith('./packages/bar'), + // testCategory: localFilePath => localFilePath.includes('/test/'), + // }, + // }, + // ], + // }, + // }, + // ); + + const queryResults = await providence(findExportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const [firstEntry, secondEntry, thirdEntry] = getEntries(queryResult); + expect(firstEntry.meta.categories).to.deep.equal(['fooCategory']); + // not mutually exclusive... + expect(secondEntry.meta.categories).to.deep.equal(['barCategory', 'testCategory']); + expect(thirdEntry.meta.categories).to.deep.equal([]); + }); }); }); -}); +} + +runFindExportsSuite({ parser: 'oxc' }); +runFindExportsSuite({ parser: 'swc' }); diff --git a/packages-node/providence-analytics/test-node/program/analyzers/find-imports.test.js b/packages-node/providence-analytics/test-node/program/analyzers/find-imports.test.js index 75b37d2825..13eef5e600 100644 --- a/packages-node/providence-analytics/test-node/program/analyzers/find-imports.test.js +++ b/packages-node/providence-analytics/test-node/program/analyzers/find-imports.test.js @@ -1,11 +1,11 @@ import { expect } from 'chai'; import { it } from 'mocha'; -import { providence } from '../../../src/program/providence.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 { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js'; import FindImportsAnalyzer from '../../../src/program/analyzers/find-imports.js'; +import { QueryService } from '../../../src/program/core/QueryService.js'; +import { providence } from '../../../src/program/providence.js'; /** * @typedef {import('../../../types/index.js').ProvidenceConfig} ProvidenceConfig @@ -13,331 +13,341 @@ import FindImportsAnalyzer from '../../../src/program/analyzers/find-imports.js' setupAnalyzerTest(); -describe('Analyzer "find-imports"', async () => { - const findImportsQueryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer); - /** @type {Partial} */ - const _providenceCfg = { - targetProjectPaths: ['/fictional/project'], // defined in mockProject - }; - - describe('Import notations', () => { - it(`supports [import 'imported/source'] (no specifiers)`, async () => { - mockProject([`import 'imported/source'`]); - - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers).to.deep.equal(['[file]']); - expect(firstEntry.result[0].source).to.equal('imported/source'); - }); - - it(`supports [import x from 'imported/source'] (default specifier)`, async () => { - mockProject([`import x from 'imported/source'`]); - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); - expect(firstEntry.result[0].source).to.equal('imported/source'); - }); - - it(`supports [import { x } from 'imported/source'] (named specifier)`, async () => { - mockProject([`import { x } from 'imported/source'`]); - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); - expect(firstEntry.result[0].importSpecifiers[1]).to.equal(undefined); - expect(firstEntry.result[0].source).to.equal('imported/source'); - }); - - it(`supports [import { x, y } from 'imported/source'] (multiple named specifiers)`, async () => { - mockProject([`import { x, y } from 'imported/source'`]); - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); - expect(firstEntry.result[0].importSpecifiers[1]).to.equal('y'); - expect(firstEntry.result[0].importSpecifiers[2]).to.equal(undefined); - expect(firstEntry.result[0].source).to.equal('imported/source'); - }); - - it(`supports [import x, { y, z } from 'imported/source'] (default and named specifiers)`, async () => { - mockProject([`import x, { y, z } from 'imported/source'`]); - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); - expect(firstEntry.result[0].importSpecifiers[1]).to.equal('y'); - expect(firstEntry.result[0].importSpecifiers[2]).to.equal('z'); - expect(firstEntry.result[0].source).to.equal('imported/source'); - }); +/** + * @param {{parser: 'oxc'|'swc'}} opts + */ +function runFindImportsSuite({ parser }) { + describe('Analyzer "find-imports"', async () => { + const findImportsQueryConfig = + await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer); + /** @type {Partial} */ + const _providenceCfg = { + targetProjectPaths: ['/fictional/project'], // defined in mockProject + parser, + }; + + describe('Import notations', () => { + it(`supports [import 'imported/source'] (no specifiers)`, async () => { + mockProject([`import 'imported/source'`]); - it(`supports [import { x as y } from 'imported/source'] (renamed specifiers)`, async () => { - mockProject([`import { x as y } from 'imported/source'`]); - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); - }); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers).to.deep.equal(['[file]']); + expect(firstEntry.result[0].source).to.equal('imported/source'); + }); - it(`supports [import * as all from 'imported/source'] (namespace specifiers)`, async () => { - mockProject([`import * as all from 'imported/source'`]); - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[*]'); - }); + it(`supports [import x from 'imported/source'] (default specifier)`, async () => { + mockProject([`import x from 'imported/source'`]); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal('imported/source'); + }); - describe('Re-exports', () => { - it(`supports [export { x } from 'imported/source'] (re-exported named specifiers)`, async () => { - mockProject([`export { x } from 'imported/source'`]); + it(`supports [import { x } from 'imported/source'] (named specifier)`, async () => { + mockProject([`import { x } from 'imported/source'`]); const queryResults = await providence(findImportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); + expect(firstEntry.result[0].importSpecifiers[1]).to.equal(undefined); + expect(firstEntry.result[0].source).to.equal('imported/source'); }); - it(`supports [export { x as y } from 'imported/source'] (re-exported renamed specifiers)`, async () => { - mockProject([`export { x as y } from 'imported/source'`]); + it(`supports [import { x, y } from 'imported/source'] (multiple named specifiers)`, async () => { + mockProject([`import { x, y } from 'imported/source'`]); const queryResults = await providence(findImportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); + expect(firstEntry.result[0].importSpecifiers[1]).to.equal('y'); + expect(firstEntry.result[0].importSpecifiers[2]).to.equal(undefined); + expect(firstEntry.result[0].source).to.equal('imported/source'); + }); + + it(`supports [import x, { y, z } from 'imported/source'] (default and named specifiers)`, async () => { + mockProject([`import x, { y, z } from 'imported/source'`]); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].importSpecifiers[1]).to.equal('y'); + expect(firstEntry.result[0].importSpecifiers[2]).to.equal('z'); + expect(firstEntry.result[0].source).to.equal('imported/source'); }); - // maybe in the future... needs experimental babel flag "exportDefaultFrom" - it.skip(`supports [export x from 'imported/source'] (re-exported default specifiers)`, async () => { - mockProject([`export x from 'imported/source'`]); + it(`supports [import { x as y } from 'imported/source'] (renamed specifiers)`, async () => { + mockProject([`import { x as y } from 'imported/source'`]); const queryResults = await providence(findImportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); }); - it(`supports [export * as x from 'imported/source'] (re-exported namespace specifiers)`, async () => { - mockProject([`export * as x from 'imported/source'`]); + it(`supports [import * as all from 'imported/source'] (namespace specifiers)`, async () => { + mockProject([`import * as all from 'imported/source'`]); const queryResults = await providence(findImportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[*]'); }); - }); - // Currently only supported for find-exports. For now not needed... - it.skip(`stores meta info(local name) of renamed specifiers`, async () => { - mockProject([`import { x as y } from 'imported/source'`]); - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - // This info will be relevant later to identify transitive relations - expect(firstEntry.result[0].localMap[0]).to.deep.equal({ - local: 'y', - imported: 'x', + describe('Re-exports', () => { + it(`supports [export { x } from 'imported/source'] (re-exported named specifiers)`, async () => { + mockProject([`export { x } from 'imported/source'`]); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); + }); + + it(`supports [export { x as y } from 'imported/source'] (re-exported renamed specifiers)`, async () => { + mockProject([`export { x as y } from 'imported/source'`]); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); + }); + + // maybe in the future... needs experimental babel flag "exportDefaultFrom" + it.skip(`supports [export x from 'imported/source'] (re-exported default specifiers)`, async () => { + mockProject([`export x from 'imported/source'`]); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('x'); + }); + + it(`supports [export * as x from 'imported/source'] (re-exported namespace specifiers)`, async () => { + mockProject([`export * as x from 'imported/source'`]); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[*]'); + }); }); - }); - it(`supports [import('my/source')] (dynamic imports)`, async () => { - mockProject([`import('my/source')`]); - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); - // TODO: somehow mark as dynamic?? - expect(firstEntry.result[0].source).to.equal('my/source'); - }); + // Currently only supported for find-exports. For now not needed... + it.skip(`stores meta info(local name) of renamed specifiers`, async () => { + mockProject([`import { x as y } from 'imported/source'`]); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + // This info will be relevant later to identify transitive relations + expect(firstEntry.result[0].localMap[0]).to.deep.equal({ + local: 'y', + imported: 'x', + }); + }); - // TODO: we can track [variable] down via trackdownId + getSourceCodeFragmentOfDeclaration - it(`supports [import(pathReference)] (dynamic imports with variable source)`, async () => { - mockProject([ - ` + it(`supports [import('my/source')] (dynamic imports)`, async () => { + mockProject([`import('my/source')`]); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); + // TODO: somehow mark as dynamic?? + expect(firstEntry.result[0].source).to.equal('my/source'); + }); + + // TODO: we can track [variable] down via trackdownId + getSourceCodeFragmentOfDeclaration + it(`supports [import(pathReference)] (dynamic imports with variable source)`, async () => { + mockProject([ + ` const pathReference = 'my/source'; import(pathReference); `, - ]); - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); - // TODO: somehow mark as dynamic?? - expect(firstEntry.result[0].source).to.equal('[variable]'); - }); - - // import styles from "./styles.css" assert { type: "css" }; - 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" };`]); - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); - expect(firstEntry.result[0].source).to.equal('@css/lib/styles.css'); - // TODO: somehow not picked up in github ci. Enable again later - // expect(firstEntry.result[0].assertionType).to.equal('css'); - }); + ]); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); + // TODO: somehow mark as dynamic?? + expect(firstEntry.result[0].source).to.equal('[variable]'); + }); - it(`supports [export styles from "@css/lib/styles.css" assert { type: "css" }] (import assertions)`, async () => { - mockProject([ - `export { styles as default } from "@css/lib/styles.css" assert { type: "css" };`, - ]); - const queryResults = await providence(findImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); - expect(firstEntry.result[0].source).to.equal('@css/lib/styles.css'); - // TODO: somehow not picked up in github ci. Enable again later - // expect(firstEntry.result[0].assertionType).to.equal('css'); - }); + // import styles from "./styles.css" assert { type: "css" }; + 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" };`]); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal('@css/lib/styles.css'); + // TODO: somehow not picked up in github ci. Enable again later + // expect(firstEntry.result[0].assertionType).to.equal('css'); + }); - describe('Filter out false positives', () => { - it(`doesn't support [object.import('my/source')] (import method members)`, async () => { - mockProject([`object.import('my/source')`]); - const queryResults = await providence(findImportsQueryConfig, { - targetProjectPaths: ['/fictional/project'], // defined in mockProject - }); + it(`supports [export styles from "@css/lib/styles.css" assert { type: "css" }] (import assertions)`, async () => { + mockProject([ + `export { styles as default } from "@css/lib/styles.css" assert { type: "css" };`, + ]); + const queryResults = await providence(findImportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); - expect(firstEntry).to.equal(undefined); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal('@css/lib/styles.css'); + // TODO: somehow not picked up in github ci. Enable again later + // expect(firstEntry.result[0].assertionType).to.equal('css'); }); - }); - /** - * Not in scope: - * - dynamic imports containing variables - * - tracking of specifier usage for default (dynamic or not) imports - */ - }); + describe('Filter out false positives', () => { + it(`doesn't support [object.import('my/source')] (import method members)`, async () => { + mockProject([`object.import('my/source')`]); + const queryResults = await providence(findImportsQueryConfig, { + targetProjectPaths: ['/fictional/project'], // defined in mockProject + }); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry).to.equal(undefined); + }); + }); + + /** + * Not in scope: + * - dynamic imports containing variables + * - tracking of specifier usage for default (dynamic or not) imports + */ + }); - describe('Default post processing', () => { - it('only stores external sources', async () => { - mockProject([ - ` + describe('Default post processing', () => { + it('only stores external sources', async () => { + mockProject([ + ` import '@external/source'; import 'external/source'; import './internal/source'; import '../internal/source'; import '../../internal/source'; `, - ]); - const queryResults = await providence(findImportsQueryConfig, { ..._providenceCfg }); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].source).to.equal('@external/source'); - expect(firstEntry.result[1].source).to.equal('external/source'); - expect(firstEntry.result[2]).to.equal(undefined); - }); - - it('normalizes source paths', async () => { - const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, { - keepInternalSources: true, + ]); + const queryResults = await providence(findImportsQueryConfig, { ..._providenceCfg }); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].source).to.equal('@external/source'); + expect(firstEntry.result[1].source).to.equal('external/source'); + expect(firstEntry.result[2]).to.equal(undefined); }); - mockProject({ - './internal/file-imports.js': ` + + it('normalizes source paths', async () => { + const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, { + keepInternalSources: true, + }); + mockProject({ + './internal/file-imports.js': ` import '@external/source'; import 'external/source'; import './source/x'; // auto resolve filename import '../'; // auto resolve root `, - './internal/source/x.js': '', - './index.js': '', + './internal/source/x.js': '', + './index.js': '', + }); + const queryResults = await providence(queryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].normalizedSource).to.equal('@external/source'); + // expect(firstEntry.result[0].fullSource).to.equal('@external/source'); + expect(firstEntry.result[1].normalizedSource).to.equal('external/source'); + // expect(firstEntry.result[1].fullSource).to.equal('external/source'); + expect(firstEntry.result[2].normalizedSource).to.equal('./source/x.js'); + // expect(firstEntry.result[2].fullSource).to.equal('./internal/source/x.js'); + expect(firstEntry.result[3].normalizedSource).to.equal('../index.js'); + // expect(firstEntry.result[3].fullSource).to.equal('./index.js'); + expect(firstEntry.result[4]).to.equal(undefined); }); - const queryResults = await providence(queryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].normalizedSource).to.equal('@external/source'); - // expect(firstEntry.result[0].fullSource).to.equal('@external/source'); - expect(firstEntry.result[1].normalizedSource).to.equal('external/source'); - // expect(firstEntry.result[1].fullSource).to.equal('external/source'); - expect(firstEntry.result[2].normalizedSource).to.equal('./source/x.js'); - // expect(firstEntry.result[2].fullSource).to.equal('./internal/source/x.js'); - expect(firstEntry.result[3].normalizedSource).to.equal('../index.js'); - // expect(firstEntry.result[3].fullSource).to.equal('./index.js'); - expect(firstEntry.result[4]).to.equal(undefined); }); - }); - describe('Options', () => { - it('"keepInternalSources"', async () => { - const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, { - keepInternalSources: true, - }); - mockProject([ - ` + describe('Options', () => { + it('"keepInternalSources"', async () => { + const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, { + keepInternalSources: true, + }); + mockProject([ + ` import '@external/source'; import 'external/source'; import './internal/source'; import '../internal/source'; import '../../internal/source'; `, - ]); - const queryResults = await providence(queryConfig, _providenceCfg); - const queryResult = queryResults[0]; - - const firstEntry = getEntry(queryResult); - expect(firstEntry.result[0].importSpecifiers.length).to.equal(1); - expect(firstEntry.result[0].source).to.equal('@external/source'); - expect(firstEntry.result[1].source).to.equal('external/source'); - expect(firstEntry.result[2].source).to.equal('./internal/source'); - expect(firstEntry.result[3].source).to.equal('../internal/source'); - expect(firstEntry.result[4].source).to.equal('../../internal/source'); - expect(firstEntry.result[5]).to.equal(undefined); - }); + ]); + const queryResults = await providence(queryConfig, _providenceCfg); + const queryResult = queryResults[0]; - // Post processors for whole result - it('"keepOriginalSourceExtensions"', async () => { - const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, { - keepOriginalSourceExtensions: true, + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].importSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].source).to.equal('@external/source'); + expect(firstEntry.result[1].source).to.equal('external/source'); + expect(firstEntry.result[2].source).to.equal('./internal/source'); + expect(firstEntry.result[3].source).to.equal('../internal/source'); + expect(firstEntry.result[4].source).to.equal('../../internal/source'); + expect(firstEntry.result[5]).to.equal(undefined); }); - mockProject([`import '@external/source.js'`, `import '@external/source';`]); - const queryResults = await providence(queryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const firstEntry = getEntry(queryResult); - const secondEntry = getEntry(queryResult, 1); + // Post processors for whole result + it('"keepOriginalSourceExtensions"', async () => { + const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, { + keepOriginalSourceExtensions: true, + }); + mockProject([`import '@external/source.js'`, `import '@external/source';`]); + const queryResults = await providence(queryConfig, _providenceCfg); + const queryResult = queryResults[0]; - expect(firstEntry.result[0].normalizedSource).to.equal('@external/source.js'); - expect(secondEntry.result[0].normalizedSource).to.equal('@external/source'); - }); + const firstEntry = getEntry(queryResult); + const secondEntry = getEntry(queryResult, 1); - // TODO: currently disabled. Might become default later (increased readability of json reports) - // but only without loss of information and once depending analyzers (match-imports and - // match-subclasses) are made compatible. - it.skip('"sortBySpecifier"', async () => { - const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, { - sortBySpecifier: true, + expect(firstEntry.result[0].normalizedSource).to.equal('@external/source.js'); + expect(secondEntry.result[0].normalizedSource).to.equal('@external/source'); }); - mockProject( - [ - `import { x, y } from '@external/source.js'`, - `import { x, y, z } from '@external/source.js'`, - ], - { filePaths: ['./file1.js', './file2.js'] }, - ); - const queryResults = await providence(queryConfig, _providenceCfg); - const queryResult = queryResults[0]; - /** - * Output will be in the format of: - * - * "queryOutput": [ - * { - * "specifier": "LitElement", - * "source": "lion-based-ui/core", - * "id": "LitElement::lion-based-ui/core", - * "dependents": [ - * "my-app-using-lion-based-ui/src/x.js", - * "my-app-using-lion-based-ui/src/y/z.js", * - * ... - */ + // TODO: currently disabled. Might become default later (increased readability of json reports) + // but only without loss of information and once depending analyzers (match-imports and + // match-subclasses) are made compatible. + it.skip('"sortBySpecifier"', async () => { + const queryConfig = await QueryService.getQueryConfigFromAnalyzer(FindImportsAnalyzer, { + sortBySpecifier: true, + }); + mockProject( + [ + `import { x, y } from '@external/source.js'`, + `import { x, y, z } from '@external/source.js'`, + ], + { filePaths: ['./file1.js', './file2.js'] }, + ); + const queryResults = await providence(queryConfig, _providenceCfg); + const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].specifier).to.equal('x'); - // Should be normalized source...? - expect(queryResult.queryOutput[0].source).to.equal('@external/source.js'); - expect(queryResult.queryOutput[0].id).to.equal('x::@external/source.js'); - expect(queryResult.queryOutput[0].dependents).to.deep.equal([ - 'fictional-project/file1.js', - 'fictional-project/file2.js', - ]); + /** + * Output will be in the format of: + * + * "queryOutput": [ + * { + * "specifier": "LitElement", + * "source": "lion-based-ui/core", + * "id": "LitElement::lion-based-ui/core", + * "dependents": [ + * "my-app-using-lion-based-ui/src/x.js", + * "my-app-using-lion-based-ui/src/y/z.js", * + * ... + */ + + expect(queryResult.queryOutput[0].specifier).to.equal('x'); + // Should be normalized source...? + expect(queryResult.queryOutput[0].source).to.equal('@external/source.js'); + expect(queryResult.queryOutput[0].id).to.equal('x::@external/source.js'); + expect(queryResult.queryOutput[0].dependents).to.deep.equal([ + 'fictional-project/file1.js', + 'fictional-project/file2.js', + ]); + }); }); }); -}); +} + +runFindImportsSuite({ parser: 'oxc' }); +runFindImportsSuite({ parser: 'swc' }); diff --git a/packages-node/providence-analytics/test-node/program/analyzers/match-imports.test.js b/packages-node/providence-analytics/test-node/program/analyzers/match-imports.test.js index 7e0063bdb4..3d644c48ee 100644 --- a/packages-node/providence-analytics/test-node/program/analyzers/match-imports.test.js +++ b/packages-node/providence-analytics/test-node/program/analyzers/match-imports.test.js @@ -13,63 +13,68 @@ import MatchImportsAnalyzer from '../../../src/program/analyzers/match-imports.j */ setupAnalyzerTest(); -// -describe('Analyzer "match-imports"', async () => { - const matchImportsQueryConfig = - await QueryService.getQueryConfigFromAnalyzer(MatchImportsAnalyzer); - /** @type {Partial} */ - const _providenceCfg = { - targetProjectPaths: ['/importing/target/project'], - referenceProjectPaths: ['/importing/target/project/node_modules/exporting-ref-project'], - }; - - // 1. Reference input data - const referenceProject = { - path: '/importing/target/project/node_modules/exporting-ref-project', - name: 'exporting-ref-project', - files: [ - // This file contains all 'original' exported definitions - { - file: './ref-src/core.js', - code: ` + +/** + * @param {{parser: 'oxc'|'swc'}} opts + */ +function runMatchImportsSuite({ parser }) { + describe('Analyzer "match-imports"', async () => { + const matchImportsQueryConfig = + await QueryService.getQueryConfigFromAnalyzer(MatchImportsAnalyzer); + /** @type {Partial} */ + const _providenceCfg = { + targetProjectPaths: ['/importing/target/project'], + referenceProjectPaths: ['/importing/target/project/node_modules/exporting-ref-project'], + parser, + }; + + // 1. Reference input data + const referenceProject = { + path: '/importing/target/project/node_modules/exporting-ref-project', + name: 'exporting-ref-project', + files: [ + // This file contains all 'original' exported definitions + { + file: './ref-src/core.js', + code: ` // named specifier export class RefClass extends HTMLElement {}; // default specifier export default class OtherClass {}; `, - }, - // This file is used to test file system 'resolvements' -> importing repos using - // `import 'exporting-ref-project/ref-src/folder'` should be pointed to this index.js file - { - file: './ref-src/folder/index.js', - code: ` + }, + // This file is used to test file system 'resolvements' -> importing repos using + // `import 'exporting-ref-project/ref-src/folder'` should be pointed to this index.js file + { + file: './ref-src/folder/index.js', + code: ` // this file (and thus this export) should be resolved via // [import 'exporting-ref-project/ref-src/folder'] export const resolvePathCorrect = null; `, - }, - { - file: './ref-component.js', - code: ` + }, + { + file: './ref-component.js', + code: ` // global effects import { RefClass } from './ref-src/core.js'; customElements.define('ref-component', RefClass); `, - }, - { - file: './not-imported.js', - code: ` + }, + { + file: './not-imported.js', + code: ` // this file will not be included by "importing-target-project" defined below export const notImported = null; `, - }, - // This file re-exports everything from 'ref-src/core.js' - { - file: './index.js', - // Default export, renamed export - // export default class X - code: ` + }, + // This file re-exports everything from 'ref-src/core.js' + { + file: './index.js', + // Default export, renamed export + // export default class X + code: ` // re-exported specifier export { RefClass } from './ref-src/core.js'; @@ -80,26 +85,26 @@ describe('Analyzer "match-imports"', async () => { import refConstImported from './ref-src/core.js'; export default refConstImported; `, - }, - { - file: './export-namespaced.js', - code: ` + }, + { + file: './export-namespaced.js', + code: ` // This file will test if all its exported specifiers are catched via "import * as" // (namespaced) export const a = 4; export default class B {}; `, - }, - ], - }; + }, + ], + }; - const searchTargetProject = { - path: '/importing/target/project', - name: 'importing-target-project', - files: [ - { - file: './target-src/indirect-imports.js', - code: ` + const searchTargetProject = { + path: '/importing/target/project', + name: 'importing-target-project', + files: [ + { + file: './target-src/indirect-imports.js', + code: ` // named import (indirect, needs transitivity check) import { RefClass } from 'exporting-ref-project'; @@ -112,10 +117,10 @@ describe('Analyzer "match-imports"', async () => { // should not be found import { nonMatched } from 'unknown-project'; `, - }, - { - file: './target-src/direct-imports.js', - code: ` + }, + { + file: './target-src/direct-imports.js', + code: ` // a direct named import import { RefClass } from 'exporting-ref-project/ref-src/core.js'; @@ -139,355 +144,386 @@ describe('Analyzer "match-imports"', async () => { // - should resolve "as folder", to 'exporting-ref-project/ref-src/folder/index.js' import { resolvePathCorrect } from 'exporting-ref-project/ref-src/folder'; `, - }, - { - file: './import-namespaced.js', - code: ` + }, + { + file: './import-namespaced.js', + code: ` // should return a match for every export in reference source import * as namespace from 'exporting-ref-project/export-namespaced.js'; `, - }, - - /** - * Possible other checks (although already tested in unit tests of find-import/find-exports): - * - dynamic imports - * - default and named specifiers in one declaration - * - renamed imports - * - ...? - */ - ], - }; - - // 2. Based on the example reference and target projects, we expect the following - // extracted specifiers to be found... - const expectedExportIdsIndirect = [ - 'RefClass::./index.js::exporting-ref-project', - 'RefRenamedClass::./index.js::exporting-ref-project', - '[default]::./index.js::exporting-ref-project', - ]; - - const expectedExportIdsDirect = [ - 'RefClass::./ref-src/core.js::exporting-ref-project', - '[default]::./ref-src/core.js::exporting-ref-project', - 'resolvePathCorrect::./ref-src/folder/index.js::exporting-ref-project', - ]; - - const expectedExportIdsNamespaced = [ - 'a::./export-namespaced.js::exporting-ref-project', - '[default]::./export-namespaced.js::exporting-ref-project', - ]; - - // eslint-disable-next-line no-unused-vars - const expectedExportIds = [ - ...expectedExportIdsIndirect, - ...expectedExportIdsDirect, - ...expectedExportIdsNamespaced, - ]; - - // 3. The AnalyzerQueryResult generated by "match-imports" - // eslint-disable-next-line no-unused-vars - const expectedMatchesOutput = [ - { - exportSpecifier: { - name: 'RefClass', - project: 'exporting-ref-project', // name under which it is registered in npm ("name" attr in package.json) - filePath: './ref-src/core.js', - id: 'RefClass::./ref-src/core.js::exporting-ref-project', - }, - // All the matched targets (files importing the specifier), ordered per project - matchesPerProject: [ - { - project: 'importing-target-project', - files: [ - './target-src/indirect-imports.js', - // ... - ], }, - // ... + + /** + * Possible other checks (although already tested in unit tests of find-import/find-exports): + * - dynamic imports + * - default and named specifiers in one declaration + * - renamed imports + * - ...? + */ ], - }, - ]; - - function testMatchedEntry(targetExportedId, queryResult, importedByFiles = []) { - const matchedEntry = queryResult.queryOutput.find( - r => r.exportSpecifier.id === targetExportedId, - ); - - const [name, filePath, project] = targetExportedId.split('::'); - expect(matchedEntry.exportSpecifier).to.deep.equal({ - name, - filePath, - project, - id: targetExportedId, - }); - expect(matchedEntry.matchesPerProject[0].project).to.equal('importing-target-project'); - expect(matchedEntry.matchesPerProject[0].files).to.deep.equal(importedByFiles); - } - - describe('Extracting exports', () => { - it(`identifies all direct export specifiers consumed by target`, async () => { - const refProject = { - path: '/target/node_modules/ref', - name: 'ref', - files: [{ file: './direct.js', code: `export default function x() {};` }], - }; - const targetProject = { - path: '/target', - name: 'target', - files: [{ file: './index.js', code: `import myFn from 'ref/direct.js';` }], - }; - mockTargetAndReferenceProject(targetProject, refProject); - - const queryResults = await providence(matchImportsQueryConfig, { - targetProjectPaths: [targetProject.path], - referenceProjectPaths: [refProject.path], - }); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput).eql([ - { - exportSpecifier: { - filePath: './direct.js', - id: '[default]::./direct.js::ref', - name: '[default]', - project: 'ref', - }, - matchesPerProject: [{ files: ['./index.js'], project: 'target' }], + }; + + // 2. Based on the example reference and target projects, we expect the following + // extracted specifiers to be found... + const expectedExportIdsIndirect = [ + 'RefClass::./index.js::exporting-ref-project', + 'RefRenamedClass::./index.js::exporting-ref-project', + '[default]::./index.js::exporting-ref-project', + ]; + + const expectedExportIdsDirect = [ + 'RefClass::./ref-src/core.js::exporting-ref-project', + '[default]::./ref-src/core.js::exporting-ref-project', + 'resolvePathCorrect::./ref-src/folder/index.js::exporting-ref-project', + ]; + + const expectedExportIdsNamespaced = [ + 'a::./export-namespaced.js::exporting-ref-project', + '[default]::./export-namespaced.js::exporting-ref-project', + ]; + + // eslint-disable-next-line no-unused-vars + const expectedExportIds = [ + ...expectedExportIdsIndirect, + ...expectedExportIdsDirect, + ...expectedExportIdsNamespaced, + ]; + + // 3. The AnalyzerQueryResult generated by "match-imports" + // eslint-disable-next-line no-unused-vars + const expectedMatchesOutput = [ + { + exportSpecifier: { + name: 'RefClass', + project: 'exporting-ref-project', // name under which it is registered in npm ("name" attr in package.json) + filePath: './ref-src/core.js', + id: 'RefClass::./ref-src/core.js::exporting-ref-project', }, - ]); - }); - - it(`identifies all indirect (transitive) export specifiers consumed by target`, async () => { - const refProject = { - path: '/target/node_modules/ref', - name: 'ref', - files: [ - { file: './direct.js', code: `export function x() {};` }, - { file: './indirect.js', code: `export { x } from './direct.js';` }, + // All the matched targets (files importing the specifier), ordered per project + matchesPerProject: [ + { + project: 'importing-target-project', + files: [ + './target-src/indirect-imports.js', + // ... + ], + }, + // ... ], - }; - const targetProject = { - path: '/target', - name: 'target', - files: [{ file: './index.js', code: `import { x } from 'ref/indirect.js';` }], - }; - mockTargetAndReferenceProject(targetProject, refProject); - const queryResults = await providence(matchImportsQueryConfig, { - targetProjectPaths: [targetProject.path], - referenceProjectPaths: [refProject.path], + }, + ]; + + function testMatchedEntry(targetExportedId, queryResult, importedByFiles = []) { + const matchedEntry = queryResult.queryOutput.find( + r => r.exportSpecifier.id === targetExportedId, + ); + + const [name, filePath, project] = targetExportedId.split('::'); + expect(matchedEntry.exportSpecifier).to.deep.equal({ + name, + filePath, + project, + id: targetExportedId, }); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput).eql([ - { - exportSpecifier: { - filePath: './indirect.js', - id: 'x::./indirect.js::ref', - name: 'x', - project: 'ref', + expect(matchedEntry.matchesPerProject[0].project).to.equal('importing-target-project'); + expect(matchedEntry.matchesPerProject[0].files).to.deep.equal(importedByFiles); + } + + describe('Extracting exports', () => { + it(`identifies all direct export specifiers consumed by target`, async () => { + const refProject = { + path: '/target/node_modules/ref', + name: 'ref', + files: [{ file: './direct.js', code: `export default function x() {};` }], + }; + const targetProject = { + path: '/target', + name: 'target', + files: [{ file: './index.js', code: `import myFn from 'ref/direct.js';` }], + }; + mockTargetAndReferenceProject(targetProject, refProject); + + const queryResults = await providence(matchImportsQueryConfig, { + targetProjectPaths: [targetProject.path], + referenceProjectPaths: [refProject.path], + }); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput).eql([ + { + exportSpecifier: { + filePath: './direct.js', + id: '[default]::./direct.js::ref', + name: '[default]', + project: 'ref', + }, + matchesPerProject: [{ files: ['./index.js'], project: 'target' }], }, - matchesPerProject: [{ files: ['./index.js'], project: 'target' }], - }, - ]); - }); + ]); + }); - it(`matches namespaced specifiers consumed by target`, async () => { - const refProject = { - path: '/target/node_modules/ref', - name: 'ref', - files: [ - { file: './namespaced.js', code: `export function x() {}; export function y() {};` }, - ], - }; - const targetProject = { - path: '/target', - name: 'target', - files: [{ file: './index.js', code: `import * as xy from 'ref/namespaced.js';` }], - }; - mockTargetAndReferenceProject(targetProject, refProject); - const queryResults = await providence(matchImportsQueryConfig, { - targetProjectPaths: [targetProject.path], - referenceProjectPaths: [refProject.path], + it(`identifies all indirect (transitive) export specifiers consumed by target`, async () => { + const refProject = { + path: '/target/node_modules/ref', + name: 'ref', + files: [ + { file: './direct.js', code: `export function x() {};` }, + { file: './indirect.js', code: `export { x } from './direct.js';` }, + ], + }; + const targetProject = { + path: '/target', + name: 'target', + files: [{ file: './index.js', code: `import { x } from 'ref/indirect.js';` }], + }; + mockTargetAndReferenceProject(targetProject, refProject); + const queryResults = await providence(matchImportsQueryConfig, { + targetProjectPaths: [targetProject.path], + referenceProjectPaths: [refProject.path], + }); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput).eql([ + { + exportSpecifier: { + filePath: './indirect.js', + id: 'x::./indirect.js::ref', + name: 'x', + project: 'ref', + }, + matchesPerProject: [{ files: ['./index.js'], project: 'target' }], + }, + ]); }); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput).eql([ - { - exportSpecifier: { - filePath: './namespaced.js', - id: 'x::./namespaced.js::ref', - name: 'x', - project: 'ref', + + it(`matches namespaced specifiers consumed by target`, async () => { + const refProject = { + path: '/target/node_modules/ref', + name: 'ref', + files: [ + { file: './namespaced.js', code: `export function x() {}; export function y() {};` }, + ], + }; + const targetProject = { + path: '/target', + name: 'target', + files: [{ file: './index.js', code: `import * as xy from 'ref/namespaced.js';` }], + }; + mockTargetAndReferenceProject(targetProject, refProject); + const queryResults = await providence(matchImportsQueryConfig, { + targetProjectPaths: [targetProject.path], + referenceProjectPaths: [refProject.path], + }); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput).eql([ + { + exportSpecifier: { + filePath: './namespaced.js', + id: 'x::./namespaced.js::ref', + name: 'x', + project: 'ref', + }, + matchesPerProject: [{ files: ['./index.js'], project: 'target' }], }, - matchesPerProject: [{ files: ['./index.js'], project: 'target' }], - }, - { - exportSpecifier: { - filePath: './namespaced.js', - id: 'y::./namespaced.js::ref', - name: 'y', - project: 'ref', + { + exportSpecifier: { + filePath: './namespaced.js', + id: 'y::./namespaced.js::ref', + name: 'y', + project: 'ref', + }, + matchesPerProject: [{ files: ['./index.js'], project: 'target' }], }, - matchesPerProject: [{ files: ['./index.js'], project: 'target' }], - }, - { - exportSpecifier: { - filePath: './namespaced.js', - id: '[file]::./namespaced.js::ref', - name: '[file]', - project: 'ref', + { + exportSpecifier: { + filePath: './namespaced.js', + id: '[file]::./namespaced.js::ref', + name: '[file]', + project: 'ref', + }, + matchesPerProject: [{ files: ['./index.js'], project: 'target' }], }, - matchesPerProject: [{ files: ['./index.js'], project: 'target' }], - }, - ]); - }); + ]); + }); - describe('Inside small example project', () => { - it(`identifies all direct export specifiers consumed by "importing-target-project"`, async () => { - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expectedExportIdsDirect.forEach(directId => { - expect( - queryResult.queryOutput.find( - exportMatchResult => exportMatchResult.exportSpecifier.id === directId, - ), - ).not.to.equal(undefined, `id '${directId}' not found`); + describe('Inside small example project', () => { + it(`identifies all direct export specifiers consumed by "importing-target-project"`, async () => { + mockTargetAndReferenceProject(searchTargetProject, referenceProject); + const queryResults = await providence(matchImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expectedExportIdsDirect.forEach(directId => { + expect( + queryResult.queryOutput.find( + exportMatchResult => exportMatchResult.exportSpecifier.id === directId, + ), + ).not.to.equal(undefined, `id '${directId}' not found`); + }); }); - }); - it(`identifies all indirect export specifiers consumed by "importing-target-project"`, async () => { - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expectedExportIdsIndirect.forEach(indirectId => { - expect( - queryResult.queryOutput.find( - exportMatchResult => exportMatchResult.exportSpecifier.id === indirectId, - ), - ).not.to.equal(undefined, `id '${indirectId}' not found`); + it(`identifies all indirect export specifiers consumed by "importing-target-project"`, async () => { + mockTargetAndReferenceProject(searchTargetProject, referenceProject); + const queryResults = await providence(matchImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expectedExportIdsIndirect.forEach(indirectId => { + expect( + queryResult.queryOutput.find( + exportMatchResult => exportMatchResult.exportSpecifier.id === indirectId, + ), + ).not.to.equal(undefined, `id '${indirectId}' not found`); + }); }); - }); - it(`matches namespaced specifiers consumed by "importing-target-project"`, async () => { - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchImportsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expectedExportIdsNamespaced.forEach(exportedSpecifierId => { - expect( - queryResult.queryOutput.find( - exportMatchResult => exportMatchResult.exportSpecifier.id === exportedSpecifierId, - ), - ).not.to.equal(undefined, `id '${exportedSpecifierId}' not found`); + it(`matches namespaced specifiers consumed by "importing-target-project"`, async () => { + mockTargetAndReferenceProject(searchTargetProject, referenceProject); + const queryResults = await providence(matchImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expectedExportIdsNamespaced.forEach(exportedSpecifierId => { + expect( + queryResult.queryOutput.find( + exportMatchResult => exportMatchResult.exportSpecifier.id === exportedSpecifierId, + ), + ).not.to.equal(undefined, `id '${exportedSpecifierId}' not found`); + }); }); }); }); - }); - describe('Matching', () => { - it(`produces a list of all matches, sorted by project`, async () => { - /** - * N.B. output structure could be simplified, since there is no need to order results by - * target project (there's only one target project per run). - * For now we keep it, so integration with dashboard stays intact. - * TODO: - * - write tests for dashboard transform logic - * - simplify output for match-* analyzers - * - adjust dashboard transfrom logic - */ - const refProject = { - path: '/target/node_modules/ref', - name: 'ref', - files: [{ file: './direct.js', code: `export default function x() {};` }], - }; - const targetProject = { - path: '/target', - name: 'target', - files: [{ file: './index.js', code: `import myFn from 'ref/direct.js';` }], - }; - mockTargetAndReferenceProject(targetProject, refProject); - const queryResults = await providence(matchImportsQueryConfig, { - targetProjectPaths: [targetProject.path], - referenceProjectPaths: [refProject.path], + describe('Matching', () => { + it(`produces a list of all matches, sorted by project`, async () => { + /** + * N.B. output structure could be simplified, since there is no need to order results by + * target project (there's only one target project per run). + * For now we keep it, so integration with dashboard stays intact. + * TODO: + * - write tests for dashboard transform logic + * - simplify output for match-* analyzers + * - adjust dashboard transfrom logic + */ + const refProject = { + path: '/target/node_modules/ref', + name: 'ref', + files: [{ file: './direct.js', code: `export default function x() {};` }], + }; + const targetProject = { + path: '/target', + name: 'target', + files: [{ file: './index.js', code: `import myFn from 'ref/direct.js';` }], + }; + mockTargetAndReferenceProject(targetProject, refProject); + const queryResults = await providence(matchImportsQueryConfig, { + targetProjectPaths: [targetProject.path], + referenceProjectPaths: [refProject.path], + }); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].matchesPerProject).eql([ + { files: ['./index.js'], project: 'target' }, + ]); }); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].matchesPerProject).eql([ - { files: ['./index.js'], project: 'target' }, - ]); - }); - it(`correctly merges/dedupes double found exports`, async () => { - const refProject = { - path: '/target/node_modules/ref', - name: 'ref', - files: [{ file: './index.js', code: `export default function x() {};` }], - }; - const targetProject = { - path: '/target', - name: 'target', - files: [ - { file: './importDefault1.js', code: `import myFn1 from 'ref/index.js';` }, - { file: './importDefault2.js', code: `import myFn2 from 'ref/index.js';` }, - ], - }; - mockTargetAndReferenceProject(targetProject, refProject); - const queryResults = await providence(matchImportsQueryConfig, { - targetProjectPaths: [targetProject.path], - referenceProjectPaths: [refProject.path], + it(`correctly merges/dedupes double found exports`, async () => { + const refProject = { + path: '/target/node_modules/ref', + name: 'ref', + files: [{ file: './index.js', code: `export default function x() {};` }], + }; + const targetProject = { + path: '/target', + name: 'target', + files: [ + { file: './importDefault1.js', code: `import myFn1 from 'ref/index.js';` }, + { file: './importDefault2.js', code: `import myFn2 from 'ref/index.js';` }, + ], + }; + mockTargetAndReferenceProject(targetProject, refProject); + const queryResults = await providence(matchImportsQueryConfig, { + targetProjectPaths: [targetProject.path], + referenceProjectPaths: [refProject.path], + }); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].exportSpecifier.name).to.equal('[default]'); + expect(queryResult.queryOutput[0].matchesPerProject).to.deep.equal([ + { files: ['./importDefault1.js', './importDefault2.js'], project: 'target' }, + ]); }); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].exportSpecifier.name).to.equal('[default]'); - expect(queryResult.queryOutput[0].matchesPerProject).to.deep.equal([ - { files: ['./importDefault1.js', './importDefault2.js'], project: 'target' }, - ]); - }); - it(`correctly merges/dedupes double found file matches when imported in different ways`, async () => { - const refProject = { - path: '/target/node_modules/ref', - name: 'ref', - files: [ - { - file: './src/core.js', - code: ` + it(`correctly merges/dedupes double found file matches when imported in different ways`, async () => { + const refProject = { + path: '/target/node_modules/ref', + name: 'ref', + files: [ + { + file: './src/core.js', + code: ` export default function x() {}; export class RefClass extends HTMLElement {} `, - }, - ], - }; - const targetProject = { - path: '/target', - name: 'target', - files: [ - { - file: './deep-imports.js', - code: ` + }, + ], + }; + const targetProject = { + path: '/target', + name: 'target', + files: [ + { + file: './deep-imports.js', + code: ` import myFn1 from 'ref/src/core.js'; import { RefClass } from 'ref/src/core.js'; import * as all from 'ref/src/core.js'; `, - }, - ], - }; - mockTargetAndReferenceProject(targetProject, refProject); - const queryResults = await providence(matchImportsQueryConfig, { - targetProjectPaths: [targetProject.path], - referenceProjectPaths: [refProject.path], + }, + ], + }; + mockTargetAndReferenceProject(targetProject, refProject); + const queryResults = await providence(matchImportsQueryConfig, { + targetProjectPaths: [targetProject.path], + referenceProjectPaths: [refProject.path], + }); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].exportSpecifier.name).to.equal('[default]'); + expect(queryResult.queryOutput[0].matchesPerProject).to.deep.equal([ + { files: ['./deep-imports.js'], project: 'target' }, + ]); + expect(queryResult.queryOutput[1].exportSpecifier.name).to.equal('RefClass'); + expect(queryResult.queryOutput[1].matchesPerProject).to.deep.equal([ + { files: ['./deep-imports.js'], project: 'target' }, + ]); + }); + + describe('Inside small example project', () => { + it(`produces a list of all matches, sorted by project`, async () => { + mockTargetAndReferenceProject(searchTargetProject, referenceProject); + const queryResults = await providence(matchImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + + expectedExportIdsDirect.forEach(targetId => { + testMatchedEntry(targetId, queryResult, ['./target-src/direct-imports.js']); + }); + + expectedExportIdsIndirect.forEach(targetId => { + testMatchedEntry(targetId, queryResult, ['./target-src/indirect-imports.js']); + }); + }); }); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].exportSpecifier.name).to.equal('[default]'); - expect(queryResult.queryOutput[0].matchesPerProject).to.deep.equal([ - { files: ['./deep-imports.js'], project: 'target' }, - ]); - expect(queryResult.queryOutput[1].exportSpecifier.name).to.equal('RefClass'); - expect(queryResult.queryOutput[1].matchesPerProject).to.deep.equal([ - { files: ['./deep-imports.js'], project: 'target' }, - ]); }); - describe('Inside small example project', () => { - it(`produces a list of all matches, sorted by project`, async () => { + describe('Configuration', () => { + it(`allows to provide results of FindExportsAnalyzer and FindImportsAnalyzer`, async () => { mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchImportsQueryConfig, _providenceCfg); + const findImportsResult = await new FindImportsAnalyzer().execute({ + targetProjectPath: searchTargetProject.path, + }); + const findExportsResult = await new FindExportsAnalyzer().execute({ + targetProjectPath: referenceProject.path, + }); + + const matchImportsQueryConfigExt = await QueryService.getQueryConfigFromAnalyzer( + MatchImportsAnalyzer, + { + targetProjectResult: findImportsResult, + referenceProjectResult: findExportsResult, + }, + ); + const queryResults = await providence(matchImportsQueryConfigExt, _providenceCfg); const queryResult = queryResults[0]; expectedExportIdsDirect.forEach(targetId => { @@ -498,39 +534,12 @@ describe('Analyzer "match-imports"', async () => { testMatchedEntry(targetId, queryResult, ['./target-src/indirect-imports.js']); }); }); - }); - }); - - describe('Configuration', () => { - it(`allows to provide results of FindExportsAnalyzer and FindImportsAnalyzer`, async () => { - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const findImportsResult = await new FindImportsAnalyzer().execute({ - targetProjectPath: searchTargetProject.path, - }); - const findExportsResult = await new FindExportsAnalyzer().execute({ - targetProjectPath: referenceProject.path, - }); - const matchImportsQueryConfigExt = await QueryService.getQueryConfigFromAnalyzer( - MatchImportsAnalyzer, - { - targetProjectResult: findImportsResult, - referenceProjectResult: findExportsResult, - }, - ); - const queryResults = await providence(matchImportsQueryConfigExt, _providenceCfg); - const queryResult = queryResults[0]; - - expectedExportIdsDirect.forEach(targetId => { - testMatchedEntry(targetId, queryResult, ['./target-src/direct-imports.js']); - }); - - expectedExportIdsIndirect.forEach(targetId => { - testMatchedEntry(targetId, queryResult, ['./target-src/indirect-imports.js']); - }); + // TODO: Test this unwind functionality in a generic MatchAnalyzer test + it.skip(`allows to provide results of FindExportsAnalyzer and FindImportsAnalyzer from external jsons`, async () => {}); }); - - // TODO: Test this unwind functionality in a generic MatchAnalyzer test - it.skip(`allows to provide results of FindExportsAnalyzer and FindImportsAnalyzer from external jsons`, async () => {}); }); -}); +} + +runMatchImportsSuite({ parser: 'oxc' }); +runMatchImportsSuite({ parser: 'swc' }); diff --git a/packages-node/providence-analytics/test-node/program/analyzers/match-paths.test.js b/packages-node/providence-analytics/test-node/program/analyzers/match-paths.test.js index a74db2aa3e..c4fdb1e9f6 100644 --- a/packages-node/providence-analytics/test-node/program/analyzers/match-paths.test.js +++ b/packages-node/providence-analytics/test-node/program/analyzers/match-paths.test.js @@ -12,24 +12,28 @@ import MatchPathsAnalyzer from '../../../src/program/analyzers/match-paths.js'; setupAnalyzerTest(); -describe('Analyzer "match-paths"', async () => { - const referenceProject = { - path: '/importing/target/project/node_modules/reference-project', - name: 'reference-project', - files: [ - { - file: './ref-src/core.js', - code: ` +/** + * @param {{parser: 'oxc'|'swc'}} opts + */ +function runMatchPathsSuite({ parser }) { + describe('Analyzer "match-paths"', async () => { + const referenceProject = { + path: '/importing/target/project/node_modules/reference-project', + name: 'reference-project', + files: [ + { + file: './ref-src/core.js', + code: ` // named specifier export class RefClass extends HTMLElement {}; // default specifier export default class OtherClass {}; `, - }, - { - file: './reexport.js', - code: ` + }, + { + file: './reexport.js', + code: ` export { RefClass as RefRenamedClass } from './ref-src/core.js'; // re-exported default specifier @@ -38,23 +42,23 @@ describe('Analyzer "match-paths"', async () => { export const Mixin = superclass => class MyMixin extends superclass {} `, - }, - { - file: './importRefClass.js', - code: ` + }, + { + file: './importRefClass.js', + code: ` import { RefClass } from './ref-src/core.js'; `, - }, - ], - }; - - const searchTargetProject = { - path: '/importing/target/project', - name: 'importing-target-project', - files: [ - { - file: './target-src/ExtendRefRenamedClass.js', - code: ` + }, + ], + }; + + const searchTargetProject = { + path: '/importing/target/project', + name: 'importing-target-project', + files: [ + { + file: './target-src/ExtendRefRenamedClass.js', + code: ` // renamed import (indirect, needs transitivity check) import { RefRenamedClass } from 'reference-project/reexport.js'; import defaultExport from 'reference-project/reexport.js'; @@ -71,10 +75,10 @@ describe('Analyzer "match-paths"', async () => { */ export class ExtendRefRenamedClass extends RefRenamedClass {} `, - }, - { - file: './target-src/direct-imports.js', - code: ` + }, + { + file: './target-src/direct-imports.js', + code: ` // a direct named import import { RefClass } from 'reference-project/ref-src/core.js'; @@ -96,10 +100,10 @@ describe('Analyzer "match-paths"', async () => { */ export class ExtendRefDefault extends RefDefault {} `, - }, - { - file: './index.js', - code: ` + }, + { + file: './index.js', + code: ` /** * This should result in: * { @@ -110,179 +114,180 @@ describe('Analyzer "match-paths"', async () => { */ export { ExtendRefDefault } from './target-src/direct-imports.js'; `, - }, - ], - }; - - const matchPathsQueryConfig = await QueryService.getQueryConfigFromAnalyzer(MatchPathsAnalyzer); - /** @type {Partial} */ - const _providenceCfg = { - targetProjectPaths: [searchTargetProject.path], - referenceProjectPaths: [referenceProject.path], - }; - - describe('Variables', () => { - const expectedMatches = [ - { - name: 'RefRenamedClass', - variable: { - from: 'RefRenamedClass', - to: 'ExtendRefRenamedClass', - paths: [ - { - from: './reexport.js', - to: './target-src/ExtendRefRenamedClass.js', - }, - { - from: 'reference-project/reexport.js', - to: './target-src/ExtendRefRenamedClass.js', - }, - ], }, - }, - { - name: '[default]', - variable: { - from: '[default]', - to: 'ExtendRefDefault', - paths: [ - { - from: './reexport.js', - to: './index.js', - }, - { - from: './ref-src/core.js', - to: './index.js', - }, - { - from: 'reference-project/reexport.js', - to: './index.js', - }, - { - from: 'reference-project/ref-src/core.js', - to: './index.js', - }, - ], - }, - }, - { - name: 'RefClass', - variable: { - from: 'RefClass', - to: 'ExtendRefClass', - paths: [ - { - from: './ref-src/core.js', - to: './target-src/direct-imports.js', - }, - { - from: 'reference-project/ref-src/core.js', - to: './target-src/direct-imports.js', - }, - ], - }, - }, - ]; - - it(`outputs an array result with from/to classes and paths`, async () => { - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput).to.deep.equal(expectedMatches); - }); + ], + }; - describe('Features', () => { - const refProj = { - path: '/importing/target/project/node_modules/reference-project', - name: 'reference-project', - files: [ - { - file: './index.js', - code: ` - export class RefClass extends HTMLElement {} - `, + const matchPathsQueryConfig = await QueryService.getQueryConfigFromAnalyzer(MatchPathsAnalyzer); + /** @type {Partial} */ + const _providenceCfg = { + targetProjectPaths: [searchTargetProject.path], + referenceProjectPaths: [referenceProject.path], + parser, + }; + + describe('Variables', () => { + const expectedMatches = [ + { + name: 'RefRenamedClass', + variable: { + from: 'RefRenamedClass', + to: 'ExtendRefRenamedClass', + paths: [ + { + from: './reexport.js', + to: './target-src/ExtendRefRenamedClass.js', + }, + { + from: 'reference-project/reexport.js', + to: './target-src/ExtendRefRenamedClass.js', + }, + ], }, - { - file: './src/importInternally.js', - code: ` - import { RefClass } from '../index.js'; - `, + }, + { + name: '[default]', + variable: { + from: '[default]', + to: 'ExtendRefDefault', + paths: [ + { + from: './reexport.js', + to: './index.js', + }, + { + from: './ref-src/core.js', + to: './index.js', + }, + { + from: 'reference-project/reexport.js', + to: './index.js', + }, + { + from: 'reference-project/ref-src/core.js', + to: './index.js', + }, + ], }, - ], - }; - - const targetProj = { - path: '/importing/target/project', - name: 'importing-target-project', - files: [ - { - file: './target-src/TargetClass.js', - // Indirect (via project root) imports - code: ` - import { RefClass } from 'reference-project'; - - export class TargetClass extends RefClass {} - `, + }, + { + name: 'RefClass', + variable: { + from: 'RefClass', + to: 'ExtendRefClass', + paths: [ + { + from: './ref-src/core.js', + to: './target-src/direct-imports.js', + }, + { + from: 'reference-project/ref-src/core.js', + to: './target-src/direct-imports.js', + }, + ], }, - ], - }; + }, + ]; - it(`identifies all "from" and "to" classes`, async () => { - mockTargetAndReferenceProject(targetProj, refProj); + it(`outputs an array result with from/to classes and paths`, async () => { + mockTargetAndReferenceProject(searchTargetProject, referenceProject); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].variable.from).to.equal('RefClass'); - expect(queryResult.queryOutput[0].variable.to).to.equal('TargetClass'); + expect(queryResult.queryOutput).to.deep.equal(expectedMatches); }); - it(`identifies all "from" and "to" paths`, async () => { - mockTargetAndReferenceProject(targetProj, refProj); - const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].variable.paths[0]).to.deep.equal({ - from: './index.js', - to: './target-src/TargetClass.js', - }); - }); + describe('Features', () => { + const refProj = { + path: '/importing/target/project/node_modules/reference-project', + name: 'reference-project', + files: [ + { + file: './index.js', + code: ` + export class RefClass extends HTMLElement {} + `, + }, + { + file: './src/importInternally.js', + code: ` + import { RefClass } from '../index.js'; + `, + }, + ], + }; - describe('"to" path of target project', () => { - const targetProjWithMultipleExports = { - ...targetProj, + const targetProj = { + path: '/importing/target/project', + name: 'importing-target-project', files: [ - ...targetProj.files, { - file: './reexportFromRoot.js', + file: './target-src/TargetClass.js', + // Indirect (via project root) imports code: ` - export { TargetClass } from './target-src/TargetClass.js'; - `, + import { RefClass } from 'reference-project'; + + export class TargetClass extends RefClass {} + `, }, ], }; - it(`gives back "to" path closest to root`, async () => { - mockTargetAndReferenceProject(targetProjWithMultipleExports, refProj); + it(`identifies all "from" and "to" classes`, async () => { + mockTargetAndReferenceProject(targetProj, refProj); + const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].variable.from).to.equal('RefClass'); + expect(queryResult.queryOutput[0].variable.to).to.equal('TargetClass'); + }); + + it(`identifies all "from" and "to" paths`, async () => { + mockTargetAndReferenceProject(targetProj, refProj); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; expect(queryResult.queryOutput[0].variable.paths[0]).to.deep.equal({ from: './index.js', - to: './reexportFromRoot.js', + to: './target-src/TargetClass.js', }); }); - it(`gives back "to" path that matches mainEntry if found`, async () => { - const targetProjWithMultipleExportsAndMainEntry = { - ...targetProjWithMultipleExports, + describe('"to" path of target project', () => { + const targetProjWithMultipleExports = { + ...targetProj, files: [ - ...targetProjWithMultipleExports.files, + ...targetProj.files, { - file: './target-src/mainEntry.js', + file: './reexportFromRoot.js', code: ` - export { TargetClass } from './TargetClass.js'; + export { TargetClass } from './target-src/TargetClass.js'; `, }, - { - file: './package.json', - code: `{ + ], + }; + + it(`gives back "to" path closest to root`, async () => { + mockTargetAndReferenceProject(targetProjWithMultipleExports, refProj); + const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].variable.paths[0]).to.deep.equal({ + from: './index.js', + to: './reexportFromRoot.js', + }); + }); + + it(`gives back "to" path that matches mainEntry if found`, async () => { + const targetProjWithMultipleExportsAndMainEntry = { + ...targetProjWithMultipleExports, + files: [ + ...targetProjWithMultipleExports.files, + { + file: './target-src/mainEntry.js', + code: ` + export { TargetClass } from './TargetClass.js'; + `, + }, + { + file: './package.json', + code: `{ "name": "${targetProjWithMultipleExports.name}", "main": "./target-src/mainEntry.js", "dependencies": { @@ -290,456 +295,460 @@ describe('Analyzer "match-paths"', async () => { } } `, - }, - ], - }; - mockTargetAndReferenceProject(targetProjWithMultipleExportsAndMainEntry, refProj); + }, + ], + }; + mockTargetAndReferenceProject(targetProjWithMultipleExportsAndMainEntry, refProj); + const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].variable.paths[0]).to.deep.equal({ + from: './index.js', + to: './target-src/mainEntry.js', + }); + }); + }); + + it(`prefixes project paths`, async () => { + mockTargetAndReferenceProject(targetProj, refProj); const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].variable.paths[0]).to.deep.equal({ + const unprefixedPaths = queryResult.queryOutput[0].variable.paths[0]; + expect(unprefixedPaths).to.deep.equal({ from: './index.js', - to: './target-src/mainEntry.js', + to: './target-src/TargetClass.js', + }); + expect(queryResult.queryOutput[0].variable.paths[1]).to.deep.equal({ + from: `${refProj.name}/${unprefixedPaths.from.slice(2)}`, + to: unprefixedPaths.to, }); }); - }); - - it(`prefixes project paths`, async () => { - mockTargetAndReferenceProject(targetProj, refProj); - const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - const unprefixedPaths = queryResult.queryOutput[0].variable.paths[0]; - expect(unprefixedPaths).to.deep.equal({ - from: './index.js', - to: './target-src/TargetClass.js', - }); - expect(queryResult.queryOutput[0].variable.paths[1]).to.deep.equal({ - from: `${refProj.name}/${unprefixedPaths.from.slice(2)}`, - to: unprefixedPaths.to, - }); - }); - it(`allows duplicate reference extensions (like "WolfRadio extends LionRadio" and + it(`allows duplicate reference extensions (like "WolfRadio extends LionRadio" and "WolfChip extends LionRadio")`, async () => { - const targetProjMultipleTargetExtensions = { - ...targetProj, - files: [ - { - file: './target-src/TargetSomething.js', - code: ` + const targetProjMultipleTargetExtensions = { + ...targetProj, + files: [ + { + file: './target-src/TargetSomething.js', + code: ` import { RefClass } from 'reference-project'; // in this case, we have TargetClass and TargetSomething => Class wins, // because of its name resemblance to RefClass export class TargetSomething extends RefClass {} `, - }, - ...targetProj.files, - ], - }; - mockTargetAndReferenceProject(targetProjMultipleTargetExtensions, refProj); - const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].variable.paths[0]).to.deep.equal({ - from: './index.js', - to: './target-src/TargetClass.js', - }); - expect(queryResult.queryOutput[1].variable.paths[0]).to.deep.equal({ - from: './index.js', - to: './target-src/TargetSomething.js', + }, + ...targetProj.files, + ], + }; + mockTargetAndReferenceProject(targetProjMultipleTargetExtensions, refProj); + const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].variable.paths[0]).to.deep.equal({ + from: './index.js', + to: './target-src/TargetClass.js', + }); + expect(queryResult.queryOutput[1].variable.paths[0]).to.deep.equal({ + from: './index.js', + to: './target-src/TargetSomething.js', + }); }); }); - }); - describe('Options', () => { - const refProj = { - path: '/importing/target/project/node_modules/reference-project', - name: 'reference-project', - files: [ - { - file: './index.js', - code: ` + describe('Options', () => { + const refProj = { + path: '/importing/target/project/node_modules/reference-project', + name: 'reference-project', + files: [ + { + file: './index.js', + code: ` export class RefClass extends HTMLElement {} `, - }, - { - file: './src/importInternally.js', - code: ` + }, + { + file: './src/importInternally.js', + code: ` import { RefClass } from '../index.js'; `, - }, - ], - }; + }, + ], + }; - const targetProj = { - path: '/importing/target/project', - name: 'importing-target-project', - files: [ - { - file: './target-src/TargetClass.js', - // Indirect (via project root) imports - code: ` + const targetProj = { + path: '/importing/target/project', + name: 'importing-target-project', + files: [ + { + file: './target-src/TargetClass.js', + // Indirect (via project root) imports + code: ` import { RefClass } from 'reference-project'; export class TargetClass extends RefClass {} `, - }, - ], - }; + }, + ], + }; - it(`filters out duplicates based on prefixes (so "WolfRadio extends LionRadio" + it(`filters out duplicates based on prefixes (so "WolfRadio extends LionRadio" is kept, "WolfChip extends LionRadio" is removed)`, async () => { - const targetProjMultipleTargetExtensions = { - ...targetProj, - files: [ - { - file: './target-src/TargetSomething.js', - code: ` + const targetProjMultipleTargetExtensions = { + ...targetProj, + files: [ + { + file: './target-src/TargetSomething.js', + code: ` import { RefClass } from 'reference-project'; // in this case, we have TargetClass and TargetSomething => Class wins, // because of its name resemblance to RefClass export class TargetSomething extends RefClass {} `, + }, + ...targetProj.files, + ], + }; + mockTargetAndReferenceProject(targetProjMultipleTargetExtensions, refProj); + const matchPathsQueryConfigFilter = await QueryService.getQueryConfigFromAnalyzer( + MatchPathsAnalyzer, + { + prefix: { from: 'ref', to: 'target' }, }, - ...targetProj.files, - ], - }; - mockTargetAndReferenceProject(targetProjMultipleTargetExtensions, refProj); - const matchPathsQueryConfigFilter = await QueryService.getQueryConfigFromAnalyzer( - MatchPathsAnalyzer, - { - prefix: { from: 'ref', to: 'target' }, - }, - ); - const queryResults = await providence(matchPathsQueryConfigFilter, _providenceCfg); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].variable.paths[0]).to.deep.equal({ - from: './index.js', - to: './target-src/TargetClass.js', + ); + const queryResults = await providence(matchPathsQueryConfigFilter, _providenceCfg); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].variable.paths[0]).to.deep.equal({ + from: './index.js', + to: './target-src/TargetClass.js', + }); + expect(queryResult.queryOutput[1]).to.equal(undefined); }); - expect(queryResult.queryOutput[1]).to.equal(undefined); }); }); - }); - describe('Tags', () => { - // eslint-disable-next-line no-shadow - const referenceProject = { - path: '/importing/target/project/node_modules/reference-project', - name: 'reference-project', - files: [ - { - file: './customelementDefinitions.js', - code: ` + describe('Tags', () => { + // eslint-disable-next-line no-shadow + const referenceProject = { + path: '/importing/target/project/node_modules/reference-project', + name: 'reference-project', + files: [ + { + file: './customelementDefinitions.js', + code: ` // => need to be replaced to imports of target project export { El1, El2 } from './classDefinitions.js'; customElements.define('el-1', El1); customElements.define('el-2', El2); `, - }, - { - file: './classDefinitions.js', - code: ` + }, + { + file: './classDefinitions.js', + code: ` export class El1 extends HTMLElement {}; export class El2 extends HTMLElement {}; `, - }, - { - file: './import.js', - code: ` + }, + { + file: './import.js', + code: ` import './customelementDefinitions.js'; `, - }, - ], - }; + }, + ], + }; - // eslint-disable-next-line no-shadow - const searchTargetProject = { - path: '/importing/target/project', - name: 'importing-target-project', - files: [ - { - file: './extendedCustomelementDefinitions.js', - code: ` + // eslint-disable-next-line no-shadow + const searchTargetProject = { + path: '/importing/target/project', + name: 'importing-target-project', + files: [ + { + file: './extendedCustomelementDefinitions.js', + code: ` import { ExtendedEl1 } from './extendedClassDefinitions.js'; import { ExtendedEl2 } from './reexportedExtendedClassDefinitions.js'; customElements.define('extended-el-1', ExtendedEl1); customElements.define('extended-el-2', ExtendedEl2); `, - }, - { - file: './extendedClassDefinitions.js', - code: ` + }, + { + file: './extendedClassDefinitions.js', + code: ` export { El1, El2 } from 'reference-project/classDefinitions.js'; export class ExtendedEl1 extends El1 {} `, - }, - { - file: './reexportedExtendedClassDefinitions.js', - code: ` + }, + { + file: './reexportedExtendedClassDefinitions.js', + code: ` export { El2 } from './extendedClassDefinitions.js'; export class ExtendedEl2 extends El2 {} `, - }, - ], - }; - - // 2. Extracted specifiers (by find-exports analyzer) - const expectedMatches = [ - { - from: 'el-1', - to: 'extended-el-1', - paths: [ - { from: './customelementDefinitions.js', to: './extendedCustomelementDefinitions.js' }, - { - from: 'reference-project/customelementDefinitions.js', - to: './extendedCustomelementDefinitions.js', }, ], - }, - { - from: 'el-2', - to: 'extended-el-2', - paths: [ - { from: './customelementDefinitions.js', to: './extendedCustomelementDefinitions.js' }, - { - from: 'reference-project/customelementDefinitions.js', - to: './extendedCustomelementDefinitions.js', - }, - ], - }, - ]; - - it(`outputs an array result with from/to tag names and paths`, async () => { - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].tag).to.deep.equal(expectedMatches[0]); - expect(queryResult.queryOutput[1].tag).to.deep.equal(expectedMatches[1]); - }); + }; - // TODO: test works in isolation, but some side effects occur when run in suite - it.skip(`allows class definition and customElement to be in same file`, async () => { - 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': ` + // 2. Extracted specifiers (by find-exports analyzer) + const expectedMatches = [ + { + from: 'el-1', + to: 'extended-el-1', + paths: [ + { from: './customelementDefinitions.js', to: './extendedCustomelementDefinitions.js' }, + { + from: 'reference-project/customelementDefinitions.js', + to: './extendedCustomelementDefinitions.js', + }, + ], + }, + { + from: 'el-2', + to: 'extended-el-2', + paths: [ + { from: './customelementDefinitions.js', to: './extendedCustomelementDefinitions.js' }, + { + from: 'reference-project/customelementDefinitions.js', + to: './extendedCustomelementDefinitions.js', + }, + ], + }, + ]; + + it(`outputs an array result with from/to tag names and paths`, async () => { + mockTargetAndReferenceProject(searchTargetProject, referenceProject); + const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].tag).to.deep.equal(expectedMatches[0]); + expect(queryResult.queryOutput[1].tag).to.deep.equal(expectedMatches[1]); + }); + + // TODO: test works in isolation, but some side effects occur when run in suite + it.skip(`allows class definition and customElement to be in same file`, async () => { + 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': ` + './demo.js': ` import { TheirTooltip } from './src/TheirTooltip.js'; import './their-button.js'; `, - }; + }; - const myProjectFiles = { - './package.json': JSON.stringify({ - name: 'my-components', - dependencies: { - 'their-components': '1.0.0', - }, - }), - './src/button/MyButton.js': ` + const myProjectFiles = { + './package.json': JSON.stringify({ + name: 'my-components', + dependencies: { + 'their-components': '1.0.0', + }, + }), + './src/button/MyButton.js': ` import { TheirButton } from 'their-components/src/TheirButton.js'; export class MyButton extends TheirButton {} customElements.define('my-button', MyButton); `, - }; - - const theirProject = { - path: '/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 })), - }; + const theirProject = { + path: '/their-components', + name: 'their-components', + files: Object.entries(theirProjectFiles).map(([file, code]) => ({ file, code })), + }; - mockTargetAndReferenceProject(theirProject, myProject); + const myProject = { + path: '/my-components', + name: 'my-components', + files: Object.entries(myProjectFiles).map(([file, code]) => ({ file, code })), + }; - const providenceCfg = { - targetProjectPaths: ['/my-components'], - referenceProjectPaths: ['/their-components'], - }; + mockTargetAndReferenceProject(theirProject, myProject); - const queryResults = await providence( - { ...matchPathsQueryConfig, prefix: { from: 'their', to: 'my' } }, - providenceCfg, - ); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].tag).to.deep.equal({ - from: 'their-button', - to: 'my-button', - paths: [ - { - from: './their-button.js', - to: './src/button/MyButton.js', - }, - { - from: 'their-components/their-button.js', - to: './src/button/MyButton.js', - }, - ], - }); - }); + const providenceCfg = { + targetProjectPaths: ['/my-components'], + referenceProjectPaths: ['/their-components'], + }; - describe('Features', () => { - it(`identifies all "from" and "to" tagnames`, async () => { - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); + const queryResults = await providence( + { ...matchPathsQueryConfig, prefix: { from: 'their', to: 'my' } }, + providenceCfg, + ); const queryResult = queryResults[0]; - 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.deep.equal({ + from: 'their-button', + to: 'my-button', + paths: [ + { + from: './their-button.js', + to: './src/button/MyButton.js', + }, + { + from: 'their-components/their-button.js', + to: './src/button/MyButton.js', + }, + ], + }); }); - it(`identifies all "from" and "to" paths`, async () => { - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].tag.paths[0]).to.deep.equal({ - from: './customelementDefinitions.js', - to: './extendedCustomelementDefinitions.js', + describe('Features', () => { + it(`identifies all "from" and "to" tagnames`, async () => { + mockTargetAndReferenceProject(searchTargetProject, referenceProject); + const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].tag.from).to.equal('el-1'); + expect(queryResult.queryOutput[0].tag.to).to.equal('extended-el-1'); }); - }); - it(`prefixes project paths`, async () => { - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput[0].tag.paths[1]).to.deep.equal({ - from: 'reference-project/customelementDefinitions.js', - to: './extendedCustomelementDefinitions.js', + it(`identifies all "from" and "to" paths`, async () => { + mockTargetAndReferenceProject(searchTargetProject, referenceProject); + const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].tag.paths[0]).to.deep.equal({ + from: './customelementDefinitions.js', + to: './extendedCustomelementDefinitions.js', + }); + }); + + it(`prefixes project paths`, async () => { + mockTargetAndReferenceProject(searchTargetProject, referenceProject); + const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput[0].tag.paths[1]).to.deep.equal({ + from: 'reference-project/customelementDefinitions.js', + to: './extendedCustomelementDefinitions.js', + }); }); }); }); - }); - describe('Full structure', () => { - const referenceProjectFull = { - ...referenceProject, - files: [ - { - file: './tag.js', - code: ` + describe('Full structure', () => { + const referenceProjectFull = { + ...referenceProject, + files: [ + { + file: './tag.js', + code: ` import { RefClass } from './ref-src/core.js'; customElements.define('ref-class', RefClass); `, - }, - ...referenceProject.files, - ], - }; + }, + ...referenceProject.files, + ], + }; - const searchTargetProjectFull = { - ...searchTargetProject, - files: [ - { - file: './tag-extended.js', - code: ` + const searchTargetProjectFull = { + ...searchTargetProject, + files: [ + { + file: './tag-extended.js', + code: ` import { ExtendRefClass } from './target-src/direct-imports'; customElements.define('tag-extended', ExtendRefClass); `, - }, - ...searchTargetProject.files, - ], - }; + }, + ...searchTargetProject.files, + ], + }; - const expectedMatchesFull = [ - { - name: 'RefRenamedClass', - variable: { - from: 'RefRenamedClass', - to: 'ExtendRefRenamedClass', - paths: [ - { - from: './reexport.js', - to: './target-src/ExtendRefRenamedClass.js', - }, - { - from: 'reference-project/reexport.js', - to: './target-src/ExtendRefRenamedClass.js', - }, - ], - }, - }, - { - name: '[default]', - variable: { - from: '[default]', - to: 'ExtendRefDefault', - paths: [ - { - from: './reexport.js', - to: './index.js', - }, - { - from: './ref-src/core.js', - to: './index.js', - }, - { - from: 'reference-project/reexport.js', - to: './index.js', - }, - { - from: 'reference-project/ref-src/core.js', - to: './index.js', - }, - ], + const expectedMatchesFull = [ + { + name: 'RefRenamedClass', + variable: { + from: 'RefRenamedClass', + to: 'ExtendRefRenamedClass', + paths: [ + { + from: './reexport.js', + to: './target-src/ExtendRefRenamedClass.js', + }, + { + from: 'reference-project/reexport.js', + to: './target-src/ExtendRefRenamedClass.js', + }, + ], + }, }, - }, - { - name: 'RefClass', - variable: { - from: 'RefClass', - to: 'ExtendRefClass', - paths: [ - { - from: './ref-src/core.js', - to: './target-src/direct-imports.js', - }, - { - from: 'reference-project/ref-src/core.js', - to: './target-src/direct-imports.js', - }, - ], + { + name: '[default]', + variable: { + from: '[default]', + to: 'ExtendRefDefault', + paths: [ + { + from: './reexport.js', + to: './index.js', + }, + { + from: './ref-src/core.js', + to: './index.js', + }, + { + from: 'reference-project/reexport.js', + to: './index.js', + }, + { + from: 'reference-project/ref-src/core.js', + to: './index.js', + }, + ], + }, }, - tag: { - from: 'ref-class', - to: 'tag-extended', - paths: [ - { - from: './tag.js', - to: './tag-extended.js', - }, - { - from: 'reference-project/tag.js', - to: './tag-extended.js', - }, - ], + { + name: 'RefClass', + variable: { + from: 'RefClass', + to: 'ExtendRefClass', + paths: [ + { + from: './ref-src/core.js', + to: './target-src/direct-imports.js', + }, + { + from: 'reference-project/ref-src/core.js', + to: './target-src/direct-imports.js', + }, + ], + }, + tag: { + from: 'ref-class', + to: 'tag-extended', + paths: [ + { + from: './tag.js', + to: './tag-extended.js', + }, + { + from: 'reference-project/tag.js', + to: './tag-extended.js', + }, + ], + }, }, - }, - ]; - - it(`outputs a "name", "variable" and "tag" entry`, async () => { - mockTargetAndReferenceProject(searchTargetProjectFull, referenceProjectFull); - const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput).to.deep.equal(expectedMatchesFull); + ]; + + it(`outputs a "name", "variable" and "tag" entry`, async () => { + mockTargetAndReferenceProject(searchTargetProjectFull, referenceProjectFull); + const queryResults = await providence(matchPathsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput).to.deep.equal(expectedMatchesFull); + }); }); }); -}); +} + +runMatchPathsSuite({ parser: 'oxc' }); +runMatchPathsSuite({ parser: 'swc' }); diff --git a/packages-node/providence-analytics/test-node/program/analyzers/match-subclasses.test.js b/packages-node/providence-analytics/test-node/program/analyzers/match-subclasses.test.js index 5a649a0261..4b75add972 100644 --- a/packages-node/providence-analytics/test-node/program/analyzers/match-subclasses.test.js +++ b/packages-node/providence-analytics/test-node/program/analyzers/match-subclasses.test.js @@ -12,28 +12,32 @@ import MatchSubclassesAnalyzer from '../../../src/program/analyzers/match-subcla setupAnalyzerTest(); -describe('Analyzer "match-subclasses"', async () => { - // 1. Reference input data - const referenceProject = { - path: '/importing/target/project/node_modules/exporting-ref-project', - name: 'exporting-ref-project', - files: [ - // This file contains all 'original' exported definitions - { - file: './ref-src/core.js', - code: ` +/** + * @param {{parser: 'oxc'|'swc'}} opts + */ +function runMatchSubclassesSuite({ parser }) { + describe('Analyzer "match-subclasses"', async () => { + // 1. Reference input data + const referenceProject = { + path: '/importing/target/project/node_modules/exporting-ref-project', + name: 'exporting-ref-project', + files: [ + // This file contains all 'original' exported definitions + { + file: './ref-src/core.js', + code: ` // named specifier export class RefClass extends HTMLElement {}; // default specifier export default class OtherClass {}; `, - }, - // This file is used to test file system 'resolvements' -> importing repos using - // `import 'exporting-ref-project/ref-src/folder'` should be pointed to this index.js file - { - file: './index.js', - code: ` + }, + // This file is used to test file system 'resolvements' -> importing repos using + // `import 'exporting-ref-project/ref-src/folder'` should be pointed to this index.js file + { + file: './index.js', + code: ` export { RefClass as RefRenamedClass } from './ref-src/core.js'; // re-exported default specifier @@ -42,28 +46,28 @@ describe('Analyzer "match-subclasses"', async () => { export const Mixin = superclass => class MyMixin extends superclass {} `, - }, - ], - }; + }, + ], + }; - const searchTargetProject = { - path: '/importing/target/project', - name: 'importing-target-project', - files: [ - { - file: './target-src/indirect-imports.js', - // Indirect (via project root) imports - code: ` + const searchTargetProject = { + path: '/importing/target/project', + name: 'importing-target-project', + files: [ + { + file: './target-src/indirect-imports.js', + // Indirect (via project root) imports + code: ` // renamed import (indirect, needs transitivity check) import { RefRenamedClass } from 'exporting-ref-project'; import defaultExport from 'exporting-ref-project'; class ExtendRefRenamedClass extends RefRenamedClass {} `, - }, - { - file: './target-src/direct-imports.js', - code: ` + }, + { + file: './target-src/direct-imports.js', + code: ` // a direct named import import { RefClass } from 'exporting-ref-project/ref-src/core.js'; @@ -80,288 +84,293 @@ describe('Analyzer "match-subclasses"', async () => { class ExtendRefDefault extends RefDefault {} class ExtendRefClassWithMixin extends ForeignMixin(Mixin(RefClass)) {} `, - }, - ], - }; + }, + ], + }; - const matchSubclassesQueryConfig = - await QueryService.getQueryConfigFromAnalyzer(MatchSubclassesAnalyzer); - /** @type {Partial} */ - const _providenceCfg = { - targetProjectPaths: [searchTargetProject.path], - referenceProjectPaths: [referenceProject.path], - }; + const matchSubclassesQueryConfig = + await QueryService.getQueryConfigFromAnalyzer(MatchSubclassesAnalyzer); + /** @type {Partial} */ + const _providenceCfg = { + targetProjectPaths: [searchTargetProject.path], + referenceProjectPaths: [referenceProject.path], + parser, + }; - // 2. Extracted specifiers (by find-exports analyzer) - const expectedExportIdsIndirect = ['RefRenamedClass::./index.js::exporting-ref-project']; + // 2. Extracted specifiers (by find-exports analyzer) + const expectedExportIdsIndirect = ['RefRenamedClass::./index.js::exporting-ref-project']; - const expectedExportIdsDirect = [ - // ids should be unique across multiple projects - // Not in scope: version number of a project. - 'RefClass::./ref-src/core.js::exporting-ref-project', - '[default]::./index.js::exporting-ref-project', - 'Mixin::./index.js::exporting-ref-project', - ]; - // eslint-disable-next-line no-unused-vars - const expectedExportIds = [...expectedExportIdsIndirect, ...expectedExportIdsDirect]; + const expectedExportIdsDirect = [ + // ids should be unique across multiple projects + // Not in scope: version number of a project. + 'RefClass::./ref-src/core.js::exporting-ref-project', + '[default]::./index.js::exporting-ref-project', + 'Mixin::./index.js::exporting-ref-project', + ]; + // eslint-disable-next-line no-unused-vars + const expectedExportIds = [...expectedExportIdsIndirect, ...expectedExportIdsDirect]; - // 3. The AnalyzerQueryResult generated by "match-subclasses" - // eslint-disable-next-line no-unused-vars - const expectedMatchesOutput = [ - { - exportSpecifier: { - name: 'RefClass', - // name under which it is registered in npm ("name" attr in package.json) - project: 'exporting-ref-project', - filePath: './ref-src/core.js', - id: 'RefClass::./ref-src/core.js::exporting-ref-project', + // 3. The AnalyzerQueryResult generated by "match-subclasses" + // eslint-disable-next-line no-unused-vars + const expectedMatchesOutput = [ + { + exportSpecifier: { + name: 'RefClass', + // name under which it is registered in npm ("name" attr in package.json) + project: 'exporting-ref-project', + filePath: './ref-src/core.js', + id: 'RefClass::./ref-src/core.js::exporting-ref-project', - // TODO: next step => identify transitive relations and add inside - // most likely via post processor - }, - // All the matched targets (files importing the specifier), ordered per project - matchesPerProject: [ - { - project: 'importing-target-project', - files: [ - { file: './target-src/indirect-imports.js', identifier: 'ExtendedRefClass' }, - // ... - ], + // TODO: next step => identify transitive relations and add inside + // most likely via post processor }, - // ... - ], - }, - ]; - - describe('Match Features', () => { - it(`identifies all directly imported class extensions`, async () => { - const refProject = { - path: '/target/node_modules/ref', - name: 'ref', - files: [{ file: './LionComp.js', code: `export class LionComp extends HTMLElement {};` }], - }; - const targetProject = { - path: '/target', - name: 'target', - files: [ + // All the matched targets (files importing the specifier), ordered per project + matchesPerProject: [ { - file: './WolfComp.js', - code: ` + project: 'importing-target-project', + files: [ + { file: './target-src/indirect-imports.js', identifier: 'ExtendedRefClass' }, + // ... + ], + }, + // ... + ], + }, + ]; + + describe('Match Features', () => { + it(`identifies all directly imported class extensions`, async () => { + const refProject = { + path: '/target/node_modules/ref', + name: 'ref', + files: [{ file: './LionComp.js', code: `export class LionComp extends HTMLElement {};` }], + }; + const targetProject = { + path: '/target', + name: 'target', + files: [ + { + file: './WolfComp.js', + code: ` import { LionComp } from 'ref/LionComp.js'; export class WolfComp extends LionComp {} `, + }, + ], + }; + mockTargetAndReferenceProject(targetProject, refProject); + const queryResults = await providence(matchSubclassesQueryConfig, { + targetProjectPaths: [targetProject.path], + referenceProjectPaths: [refProject.path], + }); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput).eql([ + { + exportSpecifier: { + filePath: './LionComp.js', + id: 'LionComp::./LionComp.js::ref', + name: 'LionComp', + project: 'ref', + }, + matchesPerProject: [ + { + files: [ + { file: './WolfComp.js', identifier: 'WolfComp', memberOverrides: undefined }, + ], + project: 'target', + }, + ], }, - ], - }; - mockTargetAndReferenceProject(targetProject, refProject); - const queryResults = await providence(matchSubclassesQueryConfig, { - targetProjectPaths: [targetProject.path], - referenceProjectPaths: [refProject.path], + ]); }); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput).eql([ - { - exportSpecifier: { - filePath: './LionComp.js', - id: 'LionComp::./LionComp.js::ref', - name: 'LionComp', - project: 'ref', - }, - matchesPerProject: [ + + it(`identifies all indirectly imported (transitive) class extensions`, async () => { + const refProject = { + path: '/target/node_modules/ref', + name: 'ref', + files: [ + { file: './LionComp.js', code: `export class LionComp extends HTMLElement {};` }, { - files: [ - { file: './WolfComp.js', identifier: 'WolfComp', memberOverrides: undefined }, - ], - project: 'target', + file: './RenamedLionComp.js', + code: `export { LionComp as RenamedLionComp } from './LionComp.js';`, }, ], - }, - ]); - }); - - it(`identifies all indirectly imported (transitive) class extensions`, async () => { - const refProject = { - path: '/target/node_modules/ref', - name: 'ref', - files: [ - { file: './LionComp.js', code: `export class LionComp extends HTMLElement {};` }, - { - file: './RenamedLionComp.js', - code: `export { LionComp as RenamedLionComp } from './LionComp.js';`, - }, - ], - }; - const targetProject = { - path: '/target', - name: 'target', - files: [ - { - file: './WolfComp2.js', - code: ` + }; + const targetProject = { + path: '/target', + name: 'target', + files: [ + { + file: './WolfComp2.js', + code: ` import { RenamedLionComp } from 'ref/RenamedLionComp.js'; export class WolfComp2 extends RenamedLionComp {} `, - }, - ], - }; - mockTargetAndReferenceProject(targetProject, refProject); - const queryResults = await providence(matchSubclassesQueryConfig, { - targetProjectPaths: [targetProject.path], - referenceProjectPaths: [refProject.path], - }); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput).eql([ - { - exportSpecifier: { - filePath: './RenamedLionComp.js', - id: 'RenamedLionComp::./RenamedLionComp.js::ref', - name: 'RenamedLionComp', - project: 'ref', - }, - matchesPerProject: [ - { - files: [ - { file: './WolfComp2.js', identifier: 'WolfComp2', memberOverrides: undefined }, - ], - project: 'target', }, ], - }, - ]); - }); - - it(`identifies Mixins`, async () => { - const refProject = { - path: '/target/node_modules/ref', - name: 'ref', - files: [ + }; + mockTargetAndReferenceProject(targetProject, refProject); + const queryResults = await providence(matchSubclassesQueryConfig, { + targetProjectPaths: [targetProject.path], + referenceProjectPaths: [refProject.path], + }); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput).eql([ { - file: './LionMixin.js', - code: ` + exportSpecifier: { + filePath: './RenamedLionComp.js', + id: 'RenamedLionComp::./RenamedLionComp.js::ref', + name: 'RenamedLionComp', + project: 'ref', + }, + matchesPerProject: [ + { + files: [ + { file: './WolfComp2.js', identifier: 'WolfComp2', memberOverrides: undefined }, + ], + project: 'target', + }, + ], + }, + ]); + }); + + it(`identifies Mixins`, async () => { + const refProject = { + path: '/target/node_modules/ref', + name: 'ref', + files: [ + { + file: './LionMixin.js', + code: ` export function LionMixin(superclass) { return class extends superclass {}; }`, - }, - ], - }; - const targetProject = { - path: '/target', - name: 'target', - files: [ - { - file: './WolfCompUsingMixin.js', - code: ` + }, + ], + }; + const targetProject = { + path: '/target', + name: 'target', + files: [ + { + file: './WolfCompUsingMixin.js', + code: ` import { LionMixin } from 'ref/LionMixin.js'; export class WolfCompUsingMixin extends LionMixin(HTMLElement) {} `, - }, - ], - }; - mockTargetAndReferenceProject(targetProject, refProject); - const queryResults = await providence(matchSubclassesQueryConfig, { - targetProjectPaths: [targetProject.path], - referenceProjectPaths: [refProject.path], - }); - const queryResult = queryResults[0]; - expect(queryResult.queryOutput).eql([ - { - exportSpecifier: { - filePath: './LionMixin.js', - id: 'LionMixin::./LionMixin.js::ref', - name: 'LionMixin', - project: 'ref', - }, - matchesPerProject: [ - { - files: [ - { - file: './WolfCompUsingMixin.js', - identifier: 'WolfCompUsingMixin', - memberOverrides: undefined, - }, - ], - project: 'target', }, ], - }, - ]); + }; + mockTargetAndReferenceProject(targetProject, refProject); + const queryResults = await providence(matchSubclassesQueryConfig, { + targetProjectPaths: [targetProject.path], + referenceProjectPaths: [refProject.path], + }); + const queryResult = queryResults[0]; + expect(queryResult.queryOutput).eql([ + { + exportSpecifier: { + filePath: './LionMixin.js', + id: 'LionMixin::./LionMixin.js::ref', + name: 'LionMixin', + project: 'ref', + }, + matchesPerProject: [ + { + files: [ + { + file: './WolfCompUsingMixin.js', + identifier: 'WolfCompUsingMixin', + memberOverrides: undefined, + }, + ], + project: 'target', + }, + ], + }, + ]); + }); }); - }); - describe('Extracting exports', () => { - describe('Inside small example project', () => { - it(`identifies all indirect export specifiers consumed by "importing-target-project"`, async () => { - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchSubclassesQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expectedExportIdsIndirect.forEach(indirectId => { - expect( - queryResult.queryOutput.find( - exportMatchResult => exportMatchResult.exportSpecifier.id === indirectId, - ), - ).not.to.equal(undefined, `id '${indirectId}' not found`); + describe('Extracting exports', () => { + describe('Inside small example project', () => { + it(`identifies all indirect export specifiers consumed by "importing-target-project"`, async () => { + mockTargetAndReferenceProject(searchTargetProject, referenceProject); + const queryResults = await providence(matchSubclassesQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expectedExportIdsIndirect.forEach(indirectId => { + expect( + queryResult.queryOutput.find( + exportMatchResult => exportMatchResult.exportSpecifier.id === indirectId, + ), + ).not.to.equal(undefined, `id '${indirectId}' not found`); + }); }); - }); - it(`identifies all direct export specifiers consumed by "importing-target-project"`, async () => { - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchSubclassesQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; - expectedExportIdsDirect.forEach(directId => { - expect( - queryResult.queryOutput.find( - exportMatchResult => exportMatchResult.exportSpecifier.id === directId, - ), - ).not.to.equal(undefined, `id '${directId}' not found`); + it(`identifies all direct export specifiers consumed by "importing-target-project"`, async () => { + mockTargetAndReferenceProject(searchTargetProject, referenceProject); + const queryResults = await providence(matchSubclassesQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + expectedExportIdsDirect.forEach(directId => { + expect( + queryResult.queryOutput.find( + exportMatchResult => exportMatchResult.exportSpecifier.id === directId, + ), + ).not.to.equal(undefined, `id '${directId}' not found`); + }); }); }); }); - }); - describe('Matching', () => { - // TODO: because we intoduced an object in match-classes, we find duplicate entries in - // our result set cretaed in macth-subclasses. Fix there... - it.skip(`produces a list of all matches, sorted by project`, async () => { - function testMatchedEntry(targetExportedId, queryResult, importedByFiles = []) { - const matchedEntry = queryResult.queryOutput.find( - r => r.exportSpecifier.id === targetExportedId, - ); + describe('Matching', () => { + // TODO: because we intoduced an object in match-classes, we find duplicate entries in + // our result set cretaed in macth-subclasses. Fix there... + it.skip(`produces a list of all matches, sorted by project`, async () => { + function testMatchedEntry(targetExportedId, queryResult, importedByFiles = []) { + const matchedEntry = queryResult.queryOutput.find( + r => r.exportSpecifier.id === targetExportedId, + ); - const [name, filePath, project] = targetExportedId.split('::'); - expect(matchedEntry.exportSpecifier).to.deep.equal({ - name, - filePath, - project, - id: targetExportedId, - }); - expect(matchedEntry.matchesPerProject[0].project).to.equal('importing-target-project'); - expect(matchedEntry.matchesPerProject[0].files).to.deep.equal(importedByFiles); - } + const [name, filePath, project] = targetExportedId.split('::'); + expect(matchedEntry.exportSpecifier).to.deep.equal({ + name, + filePath, + project, + id: targetExportedId, + }); + expect(matchedEntry.matchesPerProject[0].project).to.equal('importing-target-project'); + expect(matchedEntry.matchesPerProject[0].files).to.deep.equal(importedByFiles); + } - mockTargetAndReferenceProject(searchTargetProject, referenceProject); - const queryResults = await providence(matchSubclassesQueryConfig, _providenceCfg); - const queryResult = queryResults[0]; + mockTargetAndReferenceProject(searchTargetProject, referenceProject); + const queryResults = await providence(matchSubclassesQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; - expectedExportIdsDirect.forEach(targetId => { - testMatchedEntry(targetId, queryResult, [ - // TODO: 'identifier' needs to be the exported name of extending class - { - identifier: targetId.split('::')[0], - file: './target-src/direct-imports.js', - memberOverrides: undefined, - }, - ]); - }); + expectedExportIdsDirect.forEach(targetId => { + testMatchedEntry(targetId, queryResult, [ + // TODO: 'identifier' needs to be the exported name of extending class + { + identifier: targetId.split('::')[0], + file: './target-src/direct-imports.js', + memberOverrides: undefined, + }, + ]); + }); - expectedExportIdsIndirect.forEach(targetId => { - testMatchedEntry(targetId, queryResult, [ - // TODO: 'identifier' needs to be the exported name of extending class - { identifier: targetId.split('::')[0], file: './target-src/indirect-imports.js' }, - ]); + expectedExportIdsIndirect.forEach(targetId => { + testMatchedEntry(targetId, queryResult, [ + // TODO: 'identifier' needs to be the exported name of extending class + { identifier: targetId.split('::')[0], file: './target-src/indirect-imports.js' }, + ]); + }); }); }); }); -}); +} + +runMatchSubclassesSuite({ parser: 'oxc' }); +runMatchSubclassesSuite({ parser: 'swc' }); diff --git a/packages-node/providence-analytics/test-node/program/utils/oxc-traverse.test.js b/packages-node/providence-analytics/test-node/program/utils/oxc-traverse.test.js index b0c6a86b6a..a73e40407f 100644 --- a/packages-node/providence-analytics/test-node/program/utils/oxc-traverse.test.js +++ b/packages-node/providence-analytics/test-node/program/utils/oxc-traverse.test.js @@ -1,9 +1,11 @@ import { expect } from 'chai'; import { it } from 'mocha'; -// @ts-ignore + import babelTraversePkg from '@babel/traverse'; +import { nameOf } from '../../../src/program/utils/ast-normalizations.js'; import { oxcTraverse } from '../../../src/program/utils/oxc-traverse.js'; import { AstService } from '../../../src/program/core/AstService.js'; +// @ts-ignore /** * @typedef {import('@swc/core').Module} SwcAstModule @@ -27,16 +29,9 @@ function gatherAllScopes(oxcAst) { return swcScopes; } -/** - * @param {*} node - */ -function nameOf(node) { - return node.value || node.name; -} - describe('oxcTraverse', () => { describe('Visitor', () => { - it('traverses an swc AST based on visitor', async () => { + it('traverses an oxc AST based on visitor', async () => { const code = `import x from 'y';`; const oxcAst = await AstService._getOxcAst(code); diff --git a/packages-node/providence-analytics/types/core/core.d.ts b/packages-node/providence-analytics/types/core/core.d.ts index 4644022e97..a1b0ed50de 100644 --- a/packages-node/providence-analytics/types/core/core.d.ts +++ b/packages-node/providence-analytics/types/core/core.d.ts @@ -173,6 +173,7 @@ export type ProvidenceConfig = { skipCheckMatchCompatibility: boolean; fallbackToBabel: boolean; fs: FsAdapter; + parser: 'swc' | 'oxc'; }; /**