diff --git a/packages-node/providence-analytics/src/program/analyzers/find-exports--legacy.js b/packages-node/providence-analytics/src/program/analyzers/find-exports--legacy.js deleted file mode 100644 index b7f8f8d6d..000000000 --- a/packages-node/providence-analytics/src/program/analyzers/find-exports--legacy.js +++ /dev/null @@ -1,272 +0,0 @@ -/* eslint-disable no-shadow, no-param-reassign */ -import pathLib from 'path'; -import babelTraverse from '@babel/traverse'; -import { Analyzer } from '../core/Analyzer.js'; -import { trackDownIdentifier } from './helpers/track-down-identifier--legacy.js'; -import { normalizeSourcePaths } from './helpers/normalize-source-paths.js'; -import { getReferencedDeclaration } from '../utils/get-source-code-fragment-of-declaration.js'; -import { LogService } from '../core/LogService.js'; - -/** - * @typedef {import('@babel/types').File} File - * @typedef {import('@babel/types').Node} Node - * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName - * @typedef {import('../../../types/index.js').FindExportsAnalyzerResult} FindExportsAnalyzerResult - * @typedef {import('../../../types/index.js').FindExportsAnalyzerEntry} FindExportsAnalyzerEntry - * @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot - * @typedef {import('./helpers/track-down-identifier.js').RootFile} RootFile - * @typedef {object} RootFileMapEntry - * @typedef {string} currentFileSpecifier this is the local name in the file we track from - * @typedef {RootFile} rootFile contains file(filePath) and specifier - * @typedef {RootFileMapEntry[]} RootFileMap - * @typedef {{ exportSpecifiers:string[]; localMap: object; source:string, __tmp: { path:string } }} FindExportsSpecifierObj - */ - -/** - * @param {FindExportsSpecifierObj[]} transformedFile - */ -async function trackdownRoot(transformedFile, relativePath, projectPath) { - const fullCurrentFilePath = pathLib.resolve(projectPath, relativePath); - for (const specObj of transformedFile) { - /** @type {RootFileMap} */ - const rootFileMap = []; - if (specObj.exportSpecifiers[0] === '[file]') { - rootFileMap.push(undefined); - } else { - /** - * './src/origin.js': `export class MyComp {}` - * './index.js:' `export { MyComp as RenamedMyComp } from './src/origin'` - * - * Goes from specifier like 'RenamedMyComp' to object for rootFileMap like: - * { - * currentFileSpecifier: 'RenamedMyComp', - * rootFile: { - * file: './src/origin.js', - * specifier: 'MyCompDefinition', - * } - * } - */ - for (const specifier of specObj.exportSpecifiers) { - let rootFile; - let localMapMatch; - if (specObj.localMap) { - localMapMatch = specObj.localMap.find(m => m.exported === specifier); - } - - // TODO: find out if possible to use trackDownIdentifierFromScope - if (specObj.source) { - // TODO: see if still needed: && (localMapMatch || specifier === '[default]') - const importedIdentifier = localMapMatch?.local || specifier; - - rootFile = await trackDownIdentifier( - specObj.source, - importedIdentifier, - fullCurrentFilePath, - projectPath, - ); - - /** @type {RootFileMapEntry} */ - const entry = { - currentFileSpecifier: specifier, - rootFile, - }; - rootFileMap.push(entry); - } else { - /** @type {RootFileMapEntry} */ - const entry = { - currentFileSpecifier: specifier, - rootFile: { file: '[current]', specifier }, - }; - rootFileMap.push(entry); - } - } - } - specObj.rootFileMap = rootFileMap; - } - return transformedFile; -} - -function cleanup(transformedFile) { - transformedFile.forEach(specObj => { - if (specObj.__tmp) { - delete specObj.__tmp; - } - }); - return transformedFile; -} - -/** - * @returns {string[]} - */ -function getExportSpecifiers(node) { - // handles default [export const g = 4]; - if (node.declaration) { - if (node.declaration.declarations) { - return [node.declaration.declarations[0].id.name]; - } - if (node.declaration.id) { - return [node.declaration.id.name]; - } - } - - // handles (re)named specifiers [export { x (as y)} from 'y']; - return node.specifiers.map(s => { - let specifier; - if (s.exported) { - // { x as y } - specifier = s.exported.name === 'default' ? '[default]' : s.exported.name; - } else { - // { x } - specifier = s.local.name; - } - return specifier; - }); -} - -/** - * @returns {object[]} - */ -function getLocalNameSpecifiers(node) { - return node.specifiers - .map(s => { - if (s.exported && s.local && s.exported.name !== s.local.name) { - return { - // if reserved keyword 'default' is used, translate it into 'providence keyword' - local: s.local.name === 'default' ? '[default]' : s.local.name, - exported: s.exported.name, - }; - } - return undefined; - }) - .filter(s => s); -} - -const isImportingSpecifier = pathOrNode => - pathOrNode.type === 'ImportDefaultSpecifier' || pathOrNode.type === 'ImportSpecifier'; - -/** - * Finds import specifiers and sources for a given ast result - * @param {File} babelAst - * @param {FindExportsConfig} config - */ -function findExportsPerAstFile(babelAst, { skipFileImports }) { - LogService.debug(`Analyzer "find-exports": started findExportsPerAstFile method`); - - // Visit AST... - - /** @type {FindExportsSpecifierObj[]} */ - const transformedFile = []; - // Unfortunately, we cannot have async functions in babel traverse. - // Therefore, we store a temp reference to path that we use later for - // async post processing (tracking down original export Identifier) - let globalScopeBindings; - - babelTraverse.default(babelAst, { - Program(babelPath) { - // enter(babelPath) { - const body = babelPath.get('body'); - if (body.length) { - globalScopeBindings = body[0].scope.bindings; - } - // }, - }, - ExportNamedDeclaration(astPath) { - const exportSpecifiers = getExportSpecifiers(astPath.node); - const localMap = getLocalNameSpecifiers(astPath.node); - const source = astPath.node.source?.value; - const entry = { exportSpecifiers, localMap, source, __tmp: { astPath } }; - if (astPath.node.assertions?.length) { - entry.assertionType = astPath.node.assertions[0].value?.value; - } - transformedFile.push(entry); - }, - ExportDefaultDeclaration(defaultExportPath) { - const exportSpecifiers = ['[default]']; - let source; - if (defaultExportPath.node.declaration?.type !== 'Identifier') { - source = defaultExportPath.node.declaration.name; - } else { - const importOrDeclPath = getReferencedDeclaration({ - referencedIdentifierName: defaultExportPath.node.declaration.name, - globalScopeBindings, - }); - if (isImportingSpecifier(importOrDeclPath)) { - source = importOrDeclPath.parentPath.node.source.value; - } - } - transformedFile.push({ exportSpecifiers, source, __tmp: { astPath: defaultExportPath } }); - }, - }); - - if (!skipFileImports) { - // Always add an entry for just the file 'relativePath' - // (since this also can be imported directly from a search target project) - transformedFile.push({ - exportSpecifiers: ['[file]'], - // source: relativePath, - }); - } - - return transformedFile; -} - -export default class FindExportsAnalyzer extends Analyzer { - /** @type {AnalyzerName} */ - static analyzerName = 'find-exports'; - - /** @type {'babel'|'swc-to-babel'} */ - static requiredAst = 'swc-to-babel'; - - /** - * Finds export specifiers and sources - * @param {FindExportsConfig} customConfig - */ - async execute(customConfig = {}) { - /** - * @typedef FindExportsConfig - * @property {boolean} [onlyInternalSources=false] - * @property {boolean} [skipFileImports=false] Instead of both focusing on specifiers like - * [import {specifier} 'lion-based-ui/foo.js'], and [import 'lion-based-ui/foo.js'] as a result, - * not list file exports - */ - const cfg = { - targetProjectPath: null, - skipFileImports: false, - ...customConfig, - }; - - /** - * Prepare - */ - const cachedAnalyzerResult = this._prepare(cfg); - if (cachedAnalyzerResult) { - return cachedAnalyzerResult; - } - - /** - * Traverse - */ - const projectPath = cfg.targetProjectPath; - - const traverseEntryFn = async (ast, { relativePath }) => { - let transformedFile = findExportsPerAstFile(ast, cfg); - - transformedFile = await normalizeSourcePaths(transformedFile, relativePath, projectPath); - transformedFile = await trackdownRoot(transformedFile, relativePath, projectPath); - transformedFile = cleanup(transformedFile); - - return { result: transformedFile }; - }; - - const queryOutput = await this._traverse({ - traverseEntryFn, - filePaths: cfg.targetFilePaths, - projectPath: cfg.targetProjectPath, - }); - - /** - * Finalize - */ - return this._finalize(queryOutput, cfg); - } -} diff --git a/packages-node/providence-analytics/src/program/analyzers/find-imports--legacy.js b/packages-node/providence-analytics/src/program/analyzers/find-imports--legacy.js deleted file mode 100644 index cff31fe5b..000000000 --- a/packages-node/providence-analytics/src/program/analyzers/find-imports--legacy.js +++ /dev/null @@ -1,189 +0,0 @@ -/* eslint-disable no-shadow, no-param-reassign */ -import babelTraverse from '@babel/traverse'; -import { isRelativeSourcePath } from '../utils/relative-source-path.js'; -import { normalizeSourcePaths } from './helpers/normalize-source-paths.js'; -import { Analyzer } from '../core/Analyzer.js'; -import { LogService } from '../core/LogService.js'; - -/** - * @typedef {import('@babel/types').File} File - * @typedef {import('@babel/types').Node} Node - * @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName - * @typedef {import('../../../types/index.js').AnalyzerConfig} AnalyzerConfig - * @typedef {import('../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult - * @typedef {import('../../../types/index.js').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry - * @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot - */ - -/** - * Options that allow to filter 'on a file basis'. - * We can also filter on the total result - */ -const /** @type {AnalyzerConfig} */ options = { - /** - * Only leaves entries with external sources: - * - keeps: '@open-wc/testing' - * - drops: '../testing' - * @param {FindImportsAnalyzerQueryOutput} result - * @param {string} targetSpecifier for instance 'LitElement' - */ - onlyExternalSources(result) { - return result.filter(entry => !isRelativeSourcePath(entry.source)); - }, - }; - -/** - * @param {Node} node - */ -function getImportOrReexportsSpecifiers(node) { - return node.specifiers.map(s => { - if ( - s.type === 'ImportDefaultSpecifier' || - s.type === 'ExportDefaultSpecifier' || - (s.type === 'ExportSpecifier' && s.exported?.name === 'default') - ) { - return '[default]'; - } - if (s.type === 'ImportNamespaceSpecifier' || s.type === 'ExportNamespaceSpecifier') { - return '[*]'; - } - if ((s.imported && s.type === 'ImportNamespaceSpecifier') || s.type === 'ImportSpecifier') { - return s.imported.name; - } - if (s.exported && s.type === 'ExportNamespaceSpecifier') { - return s.exported.name; - } - return s.local.name; - }); -} - -/** - * Finds import specifiers and sources - * @param {File} babelAst - */ -function findImportsPerAstFile(babelAst, context) { - LogService.debug(`Analyzer "find-imports": started findImportsPerAstFile method`); - - // https://github.com/babel/babel/blob/672a58660f0b15691c44582f1f3fdcdac0fa0d2f/packages/babel-core/src/transformation/index.ts#L110 - // Visit AST... - /** @type {Partial[]} */ - const transformedFile = []; - babelTraverse.default(babelAst, { - ImportDeclaration(path) { - const importSpecifiers = getImportOrReexportsSpecifiers(path.node); - if (!importSpecifiers.length) { - importSpecifiers.push('[file]'); // apparently, there was just a file import - } - const source = path.node.source.value; - const entry = /** @type {Partial} */ ({ importSpecifiers, source }); - if (path.node.assertions?.length) { - entry.assertionType = path.node.assertions[0].value?.value; - } - transformedFile.push(entry); - }, - // Dynamic imports - CallExpression(path) { - if (path.node.callee?.type !== 'Import') { - return; - } - // TODO: check for specifiers catched via obj destructuring? - // TODO: also check for ['file'] - const importSpecifiers = ['[default]']; - let source = path.node.arguments[0].value; - if (!source) { - // TODO: with advanced retrieval, we could possibly get the value - source = '[variable]'; - } - transformedFile.push({ importSpecifiers, source }); - }, - ExportNamedDeclaration(path) { - if (!path.node.source) { - return; // we are dealing with a regular export, not a reexport - } - const importSpecifiers = getImportOrReexportsSpecifiers(path.node); - const source = path.node.source.value; - const entry = /** @type {Partial} */ ({ importSpecifiers, source }); - if (path.node.assertions?.length) { - entry.assertionType = path.node.assertions[0].value?.value; - } - transformedFile.push(entry); - }, - // ExportAllDeclaration(path) { - // if (!path.node.source) { - // return; // we are dealing with a regular export, not a reexport - // } - // const importSpecifiers = ['[*]']; - // const source = path.node.source.value; - // transformedFile.push({ importSpecifiers, source }); - // }, - }); - - return transformedFile; -} - -export default class FindImportsAnalyzer extends Analyzer { - /** @type {AnalyzerName} */ - static analyzerName = 'find-imports'; - - /** @type {'babel'|'swc-to-babel'} */ - requiredAst = 'swc-to-babel'; - - /** - * Finds import specifiers and sources - * @param {FindImportsConfig} customConfig - */ - async execute(customConfig = {}) { - /** - * @typedef FindImportsConfig - * @property {boolean} [keepInternalSources=false] by default, relative paths like '../x.js' are - * filtered out. This option keeps them. - * means that 'external-dep/file' will be resolved to 'external-dep/file.js' will both be stored - * as the latter - */ - const cfg = { - targetProjectPath: null, - // post process file - keepInternalSources: false, - ...customConfig, - }; - - /** - * Prepare - */ - const cachedAnalyzerResult = this._prepare(cfg); - if (cachedAnalyzerResult) { - return cachedAnalyzerResult; - } - - /** - * Traverse - */ - const queryOutput = await this._traverse(async (ast, context) => { - let transformedFile = findImportsPerAstFile(ast, context); - // Post processing based on configuration... - transformedFile = await normalizeSourcePaths( - transformedFile, - context.relativePath, - cfg.targetProjectPath, - ); - - if (!cfg.keepInternalSources) { - transformedFile = options.onlyExternalSources(transformedFile); - } - - return { result: transformedFile }; - }); - - // if (cfg.sortBySpecifier) { - // queryOutput = sortBySpecifier.execute(queryOutput, { - // ...cfg, - // specifiersKey: 'importSpecifiers', - // }); - // } - - /** - * Finalize - */ - return this._finalize(queryOutput, cfg); - } -} 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 efa010ae2..3f44e878c 100644 --- a/packages-node/providence-analytics/src/program/analyzers/find-imports.js +++ b/packages-node/providence-analytics/src/program/analyzers/find-imports.js @@ -20,6 +20,7 @@ import { LogService } from '../core/LogService.js'; * @param {SwcNode} node */ function getImportOrReexportsSpecifiers(node) { + // @ts-expect-error return node.specifiers.map(s => { if ( s.type === 'ImportDefaultSpecifier' || @@ -40,7 +41,7 @@ function getImportOrReexportsSpecifiers(node) { * Finds import specifiers and sources * @param {SwcAstModule} swcAst */ -function findImportsPerAstFile(swcAst, context) { +function findImportsPerAstFile(swcAst) { LogService.debug(`Analyzer "find-imports": started findImportsPerAstFile method`); // https://github.com/babel/babel/blob/672a58660f0b15691c44582f1f3fdcdac0fa0d2f/packages/babel-core/src/transformation/index.ts#L110 @@ -129,15 +130,18 @@ export default class FindImportsSwcAnalyzer extends Analyzer { * Traverse */ const queryOutput = await this._traverse(async (swcAst, context) => { - let transformedFile = findImportsPerAstFile(swcAst, context); + // @ts-expect-error + let transformedFile = findImportsPerAstFile(swcAst); // Post processing based on configuration... transformedFile = await normalizeSourcePaths( transformedFile, context.relativePath, + // @ts-expect-error cfg.targetProjectPath, ); if (!cfg.keepInternalSources) { + // @ts-expect-error transformedFile = transformedFile.filter(entry => !isRelativeSourcePath(entry.source)); } diff --git a/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier--legacy.js b/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier--legacy.js index 4b53cc48f..504a5cf3f 100644 --- a/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier--legacy.js +++ b/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier--legacy.js @@ -1,3 +1,5 @@ +/* eslint-disable no-shadow */ +// @ts-nocheck import fs from 'fs'; import pathLib from 'path'; import babelTraverse from '@babel/traverse'; diff --git a/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier.js b/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier.js index da4a41eef..77384a91b 100644 --- a/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier.js +++ b/packages-node/providence-analytics/src/program/analyzers/helpers/track-down-identifier.js @@ -39,16 +39,16 @@ function isExternalProject(source, projectName) { * Other than with import, no binding is created for MyClass by Babel(?) * This means 'path.scope.getBinding('MyClass')' returns undefined * and we have to find a different way to retrieve this value. - * @param {SwcPath} astPath Babel ast traversal path + * @param {SwcPath} swcPath Babel ast traversal path * @param {IdentifierName} identifierName the name that should be tracked (and that exists inside scope of astPath) */ -function getBindingAndSourceReexports(astPath, identifierName) { +function getBindingAndSourceReexports(swcPath, identifierName) { // Get to root node of file and look for exports like `export { identifierName } from 'src';` let source; let bindingType; let bindingPath; - let curPath = astPath; + let curPath = swcPath; while (curPath.parentPath) { curPath = curPath.parentPath; } 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 e16923f4c..d3f3c24ca 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 @@ -6,8 +6,8 @@ import { trackDownIdentifier } from '../analyzers/helpers/track-down-identifier. import { toPosixPath } from './to-posix-path.js'; /** - * @typedef {import('@babel/types').Node} Node - * @typedef {import('@babel/traverse').NodePath} NodePath + * @typedef {import('@swc/core').Node} SwcNode + * @typedef {import('../../../types/index.js').SwcPath} SwcPath * @typedef {import('../../../types/index.js').SwcBinding} SwcBinding * @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot @@ -39,13 +39,10 @@ export function getFilePathOrExternalSource({ rootPath, localPath }) { * - Is it a ref? Call ourselves with referencedIdentifierName ('x' in example above) * - is it a non ref declaration? Return the path of the node * @param {{ referencedIdentifierName:string, globalScopeBindings:{[key:string]:SwcBinding}; }} opts - * @returns {NodePath} + * @returns {SwcPath|null} */ export function getReferencedDeclaration({ referencedIdentifierName, globalScopeBindings }) { // We go from referencedIdentifierName 'y' to binding (VariableDeclarator path) 'y'; - // const [, refDeclaratorBinding] = - // Object.entries(globalScopeBindings).find(([key]) => key === referencedIdentifierName) || []; - const refDeclaratorBinding = globalScopeBindings[referencedIdentifierName]; // We provided a referencedIdentifierName that is not in the globalScopeBindings @@ -80,7 +77,7 @@ export function getReferencedDeclaration({ referencedIdentifierName, globalScope * ``` * * @param {{ filePath: PathFromSystemRoot; exportedIdentifier: string; projectRootPath: PathFromSystemRoot }} opts - * @returns {Promise<{ sourceNodePath: string; sourceFragment: string|null; externalImportSource: string; }>} + * @returns {Promise<{ sourceNodePath: SwcPath; sourceFragment: string|null; externalImportSource: string|null; }>} */ export async function getSourceCodeFragmentOfDeclaration({ filePath, @@ -94,7 +91,7 @@ export async function getSourceCodeFragmentOfDeclaration({ // TODO: fix swc-to-babel lib to make this compatible with 'swc-to-babel' mode of getAst const swcAst = AstService._getSwcAst(code); - /** @type {NodePath} */ + /** @type {SwcPath} */ let finalNodePath; swcTraverse( @@ -114,20 +111,25 @@ export async function getSourceCodeFragmentOfDeclaration({ const globalScopeBindings = getPathFromNode(astPath.node.body?.[0])?.scope.bindings; if (exportedIdentifier === '[default]') { - const defaultExportPath = getPathFromNode( - astPath.node.body.find(child => - ['ExportDefaultDeclaration', 'ExportDefaultExpression'].includes(child.type), - ), + const defaultExportPath = /** @type {SwcPath} */ ( + getPathFromNode( + astPath.node.body.find((/** @type {{ type: string; }} */ child) => + ['ExportDefaultDeclaration', 'ExportDefaultExpression'].includes(child.type), + ), + ) ); const isReferenced = defaultExportPath?.node.expression?.type === 'Identifier'; if (!isReferenced) { finalNodePath = defaultExportPath.get('decl') || defaultExportPath.get('expression'); } else { - finalNodePath = getReferencedDeclaration({ - referencedIdentifierName: defaultExportPath.node.expression.value, - globalScopeBindings, - }); + finalNodePath = /** @type {SwcPath} */ ( + getReferencedDeclaration({ + referencedIdentifierName: defaultExportPath.node.expression.value, + // @ts-expect-error + globalScopeBindings, + }) + ); } } else { const variableDeclaratorPath = astPath.scope.bindings[exportedIdentifier].path; @@ -145,10 +147,13 @@ export async function getSourceCodeFragmentOfDeclaration({ // it must be an exported declaration finalNodePath = contentPath; } else { - finalNodePath = getReferencedDeclaration({ - referencedIdentifierName: name, - globalScopeBindings, - }); + finalNodePath = /** @type {SwcPath} */ ( + getReferencedDeclaration({ + referencedIdentifierName: name, + // @ts-expect-error + globalScopeBindings, + }) + ); } } }, @@ -156,9 +161,12 @@ export async function getSourceCodeFragmentOfDeclaration({ { needsAdvancedPaths: true }, ); + // @ts-expect-error if (finalNodePath.type === 'ImportSpecifier') { + // @ts-expect-error const importDeclNode = finalNodePath.parentPath.node; const source = importDeclNode.source.value; + // @ts-expect-error const identifierName = finalNodePath.node.imported?.value || finalNodePath.node.local?.value; const currentFilePath = filePath; @@ -170,13 +178,14 @@ export async function getSourceCodeFragmentOfDeclaration({ ); const filePathOrSrc = getFilePathOrExternalSource({ rootPath: projectRootPath, - localPath: rootFile.file, + localPath: /** @type {PathRelativeFromProjectRoot} */ (rootFile.file), }); // TODO: allow resolving external project file paths if (!filePathOrSrc.startsWith('/')) { // So we have external project; smth like '@lion/input/x.js' return { + // @ts-expect-error sourceNodePath: finalNodePath, sourceFragment: null, externalImportSource: filePathOrSrc, @@ -184,17 +193,20 @@ export async function getSourceCodeFragmentOfDeclaration({ } return getSourceCodeFragmentOfDeclaration({ - filePath: filePathOrSrc, + filePath: /** @type {PathFromSystemRoot} */ (filePathOrSrc), exportedIdentifier: rootFile.specifier, projectRootPath, }); } return { + // @ts-expect-error sourceNodePath: finalNodePath, sourceFragment: code.slice( - finalNodePath.node?.span?.start - 1 - offset, - finalNodePath.node?.span?.end - 1 - offset, + // @ts-expect-error + finalNodePath.node.span.start - 1 - offset, + // @ts-expect-error + finalNodePath.node.span.end - 1 - offset, ), // sourceFragment: finalNodePath.node?.raw || finalNodePath.node?.value, externalImportSource: null, diff --git a/packages-node/providence-analytics/src/program/utils/resolve-import-path.js b/packages-node/providence-analytics/src/program/utils/resolve-import-path.js index 112625879..ab092c20a 100644 --- a/packages-node/providence-analytics/src/program/utils/resolve-import-path.js +++ b/packages-node/providence-analytics/src/program/utils/resolve-import-path.js @@ -1,4 +1,4 @@ -import { isBuiltin } from 'node:module'; +import { isBuiltin } from 'module'; import path from 'path'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import { LogService } from '../core/LogService.js'; diff --git a/packages-node/providence-analytics/src/program/utils/swc-traverse.js b/packages-node/providence-analytics/src/program/utils/swc-traverse.js index bee5091ef..90a4b0c03 100644 --- a/packages-node/providence-analytics/src/program/utils/swc-traverse.js +++ b/packages-node/providence-analytics/src/program/utils/swc-traverse.js @@ -71,6 +71,7 @@ export function getPathFromNode(node) { * @returns {SwcPath} */ function createSwcPath(node, parent, stop, scope) { + /** @type {SwcPath} */ const swcPath = { node, parent, @@ -78,9 +79,9 @@ function createSwcPath(node, parent, stop, scope) { // TODO: "pre-traverse" the missing scope parts instead via getter that adds refs and bindings for current scope scope, parentPath: parent ? getPathFromNode(parent) : null, - get(/** @type {string} */ name) { - const swcPathForNode = getPathFromNode(node[name]); - if (node[name] && !swcPathForNode) { + get(/** @type {string} */ id) { + const swcPathForNode = getPathFromNode(node[id]); + if (node[id] && !swcPathForNode) { // throw new Error( // `[swcTraverse]: Use {needsAdvancedPaths: true} to find path for node: ${node[name]}`, // ); diff --git a/packages-node/providence-analytics/test-node/program/analyzers/find-exports.test--legacy.js b/packages-node/providence-analytics/test-node/program/analyzers/find-exports.test--legacy.js deleted file mode 100644 index 02ca6ece5..000000000 --- a/packages-node/providence-analytics/test-node/program/analyzers/find-exports.test--legacy.js +++ /dev/null @@ -1,330 +0,0 @@ -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, getEntries } from '../../../test-helpers/mock-project-helpers.js'; -import FindExportsAnalyzer from '../../../src/program/analyzers/find-exports.js'; - -/** - * @typedef {import('../../../types/index.js').ProvidenceConfig} ProvidenceConfig - */ - -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.eql(['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.eql(['[default]']); - expect(firstResult.source).to.be.undefined; - }); - - it(`supports [export default fn(){}] (default export)`, async () => { - mockProject([`export default x => x * 3`]); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstResult = getEntry(queryResults[0]).result[0]; - - expect(firstResult.exportSpecifiers).to.eql(['[default]']); - expect(firstResult.source).to.equal(undefined); - }); - - 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.eql({ - exportSpecifiers: ['[default]'], - source: undefined, - rootFileMap: [ - { - currentFileSpecifier: '[default]', - rootFile: { file: '[current]', specifier: '[default]' }, - }, - ], - }); - - const secondEntry = getEntry(queryResults[0], 1); - expect(secondEntry.result[0]).to.eql({ - 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] (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 [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 [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.eql({ - currentFileSpecifier: '[default]', - rootFile: { - file: './styles.css', - specifier: '[default]', - }, - }); - }); - - 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.eql({ - 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.eql([ - { - local: 'x', - exported: 'y', - }, - ]); - }); - - 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.eql(['x', 'y']); - expect(firstEntry.result[0].source).to.equal('my/source'); - }); - - 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); - const firstEntry = getEntry(queryResults[0]); - const secondEntry = getEntry(queryResults[0], 1); - const thirdEntry = getEntry(queryResults[0], 2); - - expect(firstEntry.result[0].rootFileMap).to.eql([ - { - 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.eql([ - { - currentFileSpecifier: 'InBetweenComp', - rootFile: { - file: './src/OriginalComp.js', - specifier: 'OriginalComp', - }, - }, - ]); - expect(thirdEntry.result[0].rootFileMap).to.eql([ - { - currentFileSpecifier: 'OriginalComp', - rootFile: { - file: '[current]', - specifier: 'OriginalComp', - }, - }, - ]); - }); - - 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': ` - import ExtendRefDefault from './src/reexport.js'; - - export default ExtendRefDefault; - `, - }); - const queryResults = await providence(findExportsQueryConfig, _providenceCfg); - const firstEntry = getEntry(queryResults[0]); - - expect(firstEntry.result[0].rootFileMap).to.eql([ - { - 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.eql(['[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; - }); - - 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. - }); - - 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.eql(['fooCategory']); - // not mutually exclusive... - expect(secondEntry.meta.categories).to.eql(['barCategory', 'testCategory']); - expect(thirdEntry.meta.categories).to.eql([]); - }); - }); -}); 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 4bc6ed8b7..cf525d208 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 @@ -301,23 +301,23 @@ describe('Analyzer "find-exports"', async () => { }, ); - 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 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]; diff --git a/packages-node/providence-analytics/tsconfig.json b/packages-node/providence-analytics/tsconfig.json index db4176df6..d9551e1d4 100644 --- a/packages-node/providence-analytics/tsconfig.json +++ b/packages-node/providence-analytics/tsconfig.json @@ -4,6 +4,6 @@ "outDir": "./dist-types", "rootDir": "." }, - "include": ["src", "types"], + "include": ["types"], "exclude": ["dist-types"] } diff --git a/packages-node/providence-analytics/types/utils/index.d.ts b/packages-node/providence-analytics/types/utils/index.d.ts index bea813e9d..0db8d6c98 100644 --- a/packages-node/providence-analytics/types/utils/index.d.ts +++ b/packages-node/providence-analytics/types/utils/index.d.ts @@ -1,7 +1,7 @@ export type SwcScope = { id: number; parentScope?: Scope; - bindings: { [key: string]: Binding }; + bindings: { [key: string]: SwcBinding }; path: SwcPath | null; _pendingRefsWithoutBinding: SwcNode[]; _isIsolatedBlockStatement: boolean; @@ -12,15 +12,17 @@ export type SwcBinding = { identifier: SwcNode; // kind: string; refs: SwcNode[]; - path: SwcPath; + path: SwcPath | null | undefined; }; export type SwcPath = { node: SwcNode; parent: SwcNode; stop: function; - scope: SwcScope; - parentPath: SwcPath; + scope: SwcScope | undefined; + parentPath: SwcPath | null | undefined; + get: (id: string) => SwcPath | undefined; + type: string; }; type SwcVisitorFn = (swcPath: SwcPath) => void;