chore: clean and lint providence
This commit is contained in:
parent
f601d59d77
commit
e7a65a1e51
12 changed files with 76 additions and 846 deletions
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<FindImportsAnalyzerEntry>[]} */
|
|
||||||
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<FindImportsAnalyzerEntry>} */ ({ 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<FindImportsAnalyzerEntry>} */ ({ 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { LogService } from '../core/LogService.js';
|
||||||
* @param {SwcNode} node
|
* @param {SwcNode} node
|
||||||
*/
|
*/
|
||||||
function getImportOrReexportsSpecifiers(node) {
|
function getImportOrReexportsSpecifiers(node) {
|
||||||
|
// @ts-expect-error
|
||||||
return node.specifiers.map(s => {
|
return node.specifiers.map(s => {
|
||||||
if (
|
if (
|
||||||
s.type === 'ImportDefaultSpecifier' ||
|
s.type === 'ImportDefaultSpecifier' ||
|
||||||
|
|
@ -40,7 +41,7 @@ function getImportOrReexportsSpecifiers(node) {
|
||||||
* Finds import specifiers and sources
|
* Finds import specifiers and sources
|
||||||
* @param {SwcAstModule} swcAst
|
* @param {SwcAstModule} swcAst
|
||||||
*/
|
*/
|
||||||
function findImportsPerAstFile(swcAst, context) {
|
function findImportsPerAstFile(swcAst) {
|
||||||
LogService.debug(`Analyzer "find-imports": started findImportsPerAstFile method`);
|
LogService.debug(`Analyzer "find-imports": started findImportsPerAstFile method`);
|
||||||
|
|
||||||
// https://github.com/babel/babel/blob/672a58660f0b15691c44582f1f3fdcdac0fa0d2f/packages/babel-core/src/transformation/index.ts#L110
|
// https://github.com/babel/babel/blob/672a58660f0b15691c44582f1f3fdcdac0fa0d2f/packages/babel-core/src/transformation/index.ts#L110
|
||||||
|
|
@ -129,15 +130,18 @@ export default class FindImportsSwcAnalyzer extends Analyzer {
|
||||||
* Traverse
|
* Traverse
|
||||||
*/
|
*/
|
||||||
const queryOutput = await this._traverse(async (swcAst, context) => {
|
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...
|
// Post processing based on configuration...
|
||||||
transformedFile = await normalizeSourcePaths(
|
transformedFile = await normalizeSourcePaths(
|
||||||
transformedFile,
|
transformedFile,
|
||||||
context.relativePath,
|
context.relativePath,
|
||||||
|
// @ts-expect-error
|
||||||
cfg.targetProjectPath,
|
cfg.targetProjectPath,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!cfg.keepInternalSources) {
|
if (!cfg.keepInternalSources) {
|
||||||
|
// @ts-expect-error
|
||||||
transformedFile = transformedFile.filter(entry => !isRelativeSourcePath(entry.source));
|
transformedFile = transformedFile.filter(entry => !isRelativeSourcePath(entry.source));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* eslint-disable no-shadow */
|
||||||
|
// @ts-nocheck
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import pathLib from 'path';
|
import pathLib from 'path';
|
||||||
import babelTraverse from '@babel/traverse';
|
import babelTraverse from '@babel/traverse';
|
||||||
|
|
|
||||||
|
|
@ -39,16 +39,16 @@ function isExternalProject(source, projectName) {
|
||||||
* Other than with import, no binding is created for MyClass by Babel(?)
|
* Other than with import, no binding is created for MyClass by Babel(?)
|
||||||
* This means 'path.scope.getBinding('MyClass')' returns undefined
|
* This means 'path.scope.getBinding('MyClass')' returns undefined
|
||||||
* and we have to find a different way to retrieve this value.
|
* and we have to find a different way to retrieve this value.
|
||||||
* @param {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)
|
* @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';`
|
// Get to root node of file and look for exports like `export { identifierName } from 'src';`
|
||||||
let source;
|
let source;
|
||||||
let bindingType;
|
let bindingType;
|
||||||
let bindingPath;
|
let bindingPath;
|
||||||
|
|
||||||
let curPath = astPath;
|
let curPath = swcPath;
|
||||||
while (curPath.parentPath) {
|
while (curPath.parentPath) {
|
||||||
curPath = curPath.parentPath;
|
curPath = curPath.parentPath;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import { trackDownIdentifier } from '../analyzers/helpers/track-down-identifier.
|
||||||
import { toPosixPath } from './to-posix-path.js';
|
import { toPosixPath } from './to-posix-path.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('@babel/types').Node} Node
|
* @typedef {import('@swc/core').Node} SwcNode
|
||||||
* @typedef {import('@babel/traverse').NodePath} NodePath
|
* @typedef {import('../../../types/index.js').SwcPath} SwcPath
|
||||||
* @typedef {import('../../../types/index.js').SwcBinding} SwcBinding
|
* @typedef {import('../../../types/index.js').SwcBinding} SwcBinding
|
||||||
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||||
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
|
* @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 ref? Call ourselves with referencedIdentifierName ('x' in example above)
|
||||||
* - is it a non ref declaration? Return the path of the node
|
* - is it a non ref declaration? Return the path of the node
|
||||||
* @param {{ referencedIdentifierName:string, globalScopeBindings:{[key:string]:SwcBinding}; }} opts
|
* @param {{ referencedIdentifierName:string, globalScopeBindings:{[key:string]:SwcBinding}; }} opts
|
||||||
* @returns {NodePath}
|
* @returns {SwcPath|null}
|
||||||
*/
|
*/
|
||||||
export function getReferencedDeclaration({ referencedIdentifierName, globalScopeBindings }) {
|
export function getReferencedDeclaration({ referencedIdentifierName, globalScopeBindings }) {
|
||||||
// We go from referencedIdentifierName 'y' to binding (VariableDeclarator path) 'y';
|
// We go from referencedIdentifierName 'y' to binding (VariableDeclarator path) 'y';
|
||||||
// const [, refDeclaratorBinding] =
|
|
||||||
// Object.entries(globalScopeBindings).find(([key]) => key === referencedIdentifierName) || [];
|
|
||||||
|
|
||||||
const refDeclaratorBinding = globalScopeBindings[referencedIdentifierName];
|
const refDeclaratorBinding = globalScopeBindings[referencedIdentifierName];
|
||||||
|
|
||||||
// We provided a referencedIdentifierName that is not in the globalScopeBindings
|
// 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
|
* @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({
|
export async function getSourceCodeFragmentOfDeclaration({
|
||||||
filePath,
|
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
|
// TODO: fix swc-to-babel lib to make this compatible with 'swc-to-babel' mode of getAst
|
||||||
const swcAst = AstService._getSwcAst(code);
|
const swcAst = AstService._getSwcAst(code);
|
||||||
|
|
||||||
/** @type {NodePath} */
|
/** @type {SwcPath} */
|
||||||
let finalNodePath;
|
let finalNodePath;
|
||||||
|
|
||||||
swcTraverse(
|
swcTraverse(
|
||||||
|
|
@ -114,20 +111,25 @@ export async function getSourceCodeFragmentOfDeclaration({
|
||||||
const globalScopeBindings = getPathFromNode(astPath.node.body?.[0])?.scope.bindings;
|
const globalScopeBindings = getPathFromNode(astPath.node.body?.[0])?.scope.bindings;
|
||||||
|
|
||||||
if (exportedIdentifier === '[default]') {
|
if (exportedIdentifier === '[default]') {
|
||||||
const defaultExportPath = getPathFromNode(
|
const defaultExportPath = /** @type {SwcPath} */ (
|
||||||
astPath.node.body.find(child =>
|
getPathFromNode(
|
||||||
|
astPath.node.body.find((/** @type {{ type: string; }} */ child) =>
|
||||||
['ExportDefaultDeclaration', 'ExportDefaultExpression'].includes(child.type),
|
['ExportDefaultDeclaration', 'ExportDefaultExpression'].includes(child.type),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
const isReferenced = defaultExportPath?.node.expression?.type === 'Identifier';
|
const isReferenced = defaultExportPath?.node.expression?.type === 'Identifier';
|
||||||
|
|
||||||
if (!isReferenced) {
|
if (!isReferenced) {
|
||||||
finalNodePath = defaultExportPath.get('decl') || defaultExportPath.get('expression');
|
finalNodePath = defaultExportPath.get('decl') || defaultExportPath.get('expression');
|
||||||
} else {
|
} else {
|
||||||
finalNodePath = getReferencedDeclaration({
|
finalNodePath = /** @type {SwcPath} */ (
|
||||||
|
getReferencedDeclaration({
|
||||||
referencedIdentifierName: defaultExportPath.node.expression.value,
|
referencedIdentifierName: defaultExportPath.node.expression.value,
|
||||||
|
// @ts-expect-error
|
||||||
globalScopeBindings,
|
globalScopeBindings,
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const variableDeclaratorPath = astPath.scope.bindings[exportedIdentifier].path;
|
const variableDeclaratorPath = astPath.scope.bindings[exportedIdentifier].path;
|
||||||
|
|
@ -145,10 +147,13 @@ export async function getSourceCodeFragmentOfDeclaration({
|
||||||
// it must be an exported declaration
|
// it must be an exported declaration
|
||||||
finalNodePath = contentPath;
|
finalNodePath = contentPath;
|
||||||
} else {
|
} else {
|
||||||
finalNodePath = getReferencedDeclaration({
|
finalNodePath = /** @type {SwcPath} */ (
|
||||||
|
getReferencedDeclaration({
|
||||||
referencedIdentifierName: name,
|
referencedIdentifierName: name,
|
||||||
|
// @ts-expect-error
|
||||||
globalScopeBindings,
|
globalScopeBindings,
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -156,9 +161,12 @@ export async function getSourceCodeFragmentOfDeclaration({
|
||||||
{ needsAdvancedPaths: true },
|
{ needsAdvancedPaths: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
if (finalNodePath.type === 'ImportSpecifier') {
|
if (finalNodePath.type === 'ImportSpecifier') {
|
||||||
|
// @ts-expect-error
|
||||||
const importDeclNode = finalNodePath.parentPath.node;
|
const importDeclNode = finalNodePath.parentPath.node;
|
||||||
const source = importDeclNode.source.value;
|
const source = importDeclNode.source.value;
|
||||||
|
// @ts-expect-error
|
||||||
const identifierName = finalNodePath.node.imported?.value || finalNodePath.node.local?.value;
|
const identifierName = finalNodePath.node.imported?.value || finalNodePath.node.local?.value;
|
||||||
const currentFilePath = filePath;
|
const currentFilePath = filePath;
|
||||||
|
|
||||||
|
|
@ -170,13 +178,14 @@ export async function getSourceCodeFragmentOfDeclaration({
|
||||||
);
|
);
|
||||||
const filePathOrSrc = getFilePathOrExternalSource({
|
const filePathOrSrc = getFilePathOrExternalSource({
|
||||||
rootPath: projectRootPath,
|
rootPath: projectRootPath,
|
||||||
localPath: rootFile.file,
|
localPath: /** @type {PathRelativeFromProjectRoot} */ (rootFile.file),
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: allow resolving external project file paths
|
// TODO: allow resolving external project file paths
|
||||||
if (!filePathOrSrc.startsWith('/')) {
|
if (!filePathOrSrc.startsWith('/')) {
|
||||||
// So we have external project; smth like '@lion/input/x.js'
|
// So we have external project; smth like '@lion/input/x.js'
|
||||||
return {
|
return {
|
||||||
|
// @ts-expect-error
|
||||||
sourceNodePath: finalNodePath,
|
sourceNodePath: finalNodePath,
|
||||||
sourceFragment: null,
|
sourceFragment: null,
|
||||||
externalImportSource: filePathOrSrc,
|
externalImportSource: filePathOrSrc,
|
||||||
|
|
@ -184,17 +193,20 @@ export async function getSourceCodeFragmentOfDeclaration({
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSourceCodeFragmentOfDeclaration({
|
return getSourceCodeFragmentOfDeclaration({
|
||||||
filePath: filePathOrSrc,
|
filePath: /** @type {PathFromSystemRoot} */ (filePathOrSrc),
|
||||||
exportedIdentifier: rootFile.specifier,
|
exportedIdentifier: rootFile.specifier,
|
||||||
projectRootPath,
|
projectRootPath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
// @ts-expect-error
|
||||||
sourceNodePath: finalNodePath,
|
sourceNodePath: finalNodePath,
|
||||||
sourceFragment: code.slice(
|
sourceFragment: code.slice(
|
||||||
finalNodePath.node?.span?.start - 1 - offset,
|
// @ts-expect-error
|
||||||
finalNodePath.node?.span?.end - 1 - offset,
|
finalNodePath.node.span.start - 1 - offset,
|
||||||
|
// @ts-expect-error
|
||||||
|
finalNodePath.node.span.end - 1 - offset,
|
||||||
),
|
),
|
||||||
// sourceFragment: finalNodePath.node?.raw || finalNodePath.node?.value,
|
// sourceFragment: finalNodePath.node?.raw || finalNodePath.node?.value,
|
||||||
externalImportSource: null,
|
externalImportSource: null,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { isBuiltin } from 'node:module';
|
import { isBuiltin } from 'module';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||||
import { LogService } from '../core/LogService.js';
|
import { LogService } from '../core/LogService.js';
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ export function getPathFromNode(node) {
|
||||||
* @returns {SwcPath}
|
* @returns {SwcPath}
|
||||||
*/
|
*/
|
||||||
function createSwcPath(node, parent, stop, scope) {
|
function createSwcPath(node, parent, stop, scope) {
|
||||||
|
/** @type {SwcPath} */
|
||||||
const swcPath = {
|
const swcPath = {
|
||||||
node,
|
node,
|
||||||
parent,
|
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
|
// TODO: "pre-traverse" the missing scope parts instead via getter that adds refs and bindings for current scope
|
||||||
scope,
|
scope,
|
||||||
parentPath: parent ? getPathFromNode(parent) : null,
|
parentPath: parent ? getPathFromNode(parent) : null,
|
||||||
get(/** @type {string} */ name) {
|
get(/** @type {string} */ id) {
|
||||||
const swcPathForNode = getPathFromNode(node[name]);
|
const swcPathForNode = getPathFromNode(node[id]);
|
||||||
if (node[name] && !swcPathForNode) {
|
if (node[id] && !swcPathForNode) {
|
||||||
// throw new Error(
|
// throw new Error(
|
||||||
// `[swcTraverse]: Use {needsAdvancedPaths: true} to find path for node: ${node[name]}`,
|
// `[swcTraverse]: Use {needsAdvancedPaths: true} to find path for node: ${node[name]}`,
|
||||||
// );
|
// );
|
||||||
|
|
|
||||||
|
|
@ -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<ProvidenceConfig>} */
|
|
||||||
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([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -301,23 +301,23 @@ describe('Analyzer "find-exports"', async () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const findExportsCategoryQueryObj = await QueryService.getQueryConfigFromAnalyzer(
|
// const findExportsCategoryQueryObj = await QueryService.getQueryConfigFromAnalyzer(
|
||||||
'find-exports',
|
// 'find-exports',
|
||||||
{
|
// {
|
||||||
metaConfig: {
|
// metaConfig: {
|
||||||
categoryConfig: [
|
// categoryConfig: [
|
||||||
{
|
// {
|
||||||
project: 'my-project',
|
// project: 'my-project',
|
||||||
categories: {
|
// categories: {
|
||||||
fooCategory: localFilePath => localFilePath.startsWith('./foo'),
|
// fooCategory: localFilePath => localFilePath.startsWith('./foo'),
|
||||||
barCategory: localFilePath => localFilePath.startsWith('./packages/bar'),
|
// barCategory: localFilePath => localFilePath.startsWith('./packages/bar'),
|
||||||
testCategory: localFilePath => localFilePath.includes('/test/'),
|
// testCategory: localFilePath => localFilePath.includes('/test/'),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
|
const queryResults = await providence(findExportsQueryConfig, _providenceCfg);
|
||||||
const queryResult = queryResults[0];
|
const queryResult = queryResults[0];
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,6 @@
|
||||||
"outDir": "./dist-types",
|
"outDir": "./dist-types",
|
||||||
"rootDir": "."
|
"rootDir": "."
|
||||||
},
|
},
|
||||||
"include": ["src", "types"],
|
"include": ["types"],
|
||||||
"exclude": ["dist-types"]
|
"exclude": ["dist-types"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export type SwcScope = {
|
export type SwcScope = {
|
||||||
id: number;
|
id: number;
|
||||||
parentScope?: Scope;
|
parentScope?: Scope;
|
||||||
bindings: { [key: string]: Binding };
|
bindings: { [key: string]: SwcBinding };
|
||||||
path: SwcPath | null;
|
path: SwcPath | null;
|
||||||
_pendingRefsWithoutBinding: SwcNode[];
|
_pendingRefsWithoutBinding: SwcNode[];
|
||||||
_isIsolatedBlockStatement: boolean;
|
_isIsolatedBlockStatement: boolean;
|
||||||
|
|
@ -12,15 +12,17 @@ export type SwcBinding = {
|
||||||
identifier: SwcNode;
|
identifier: SwcNode;
|
||||||
// kind: string;
|
// kind: string;
|
||||||
refs: SwcNode[];
|
refs: SwcNode[];
|
||||||
path: SwcPath;
|
path: SwcPath | null | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SwcPath = {
|
export type SwcPath = {
|
||||||
node: SwcNode;
|
node: SwcNode;
|
||||||
parent: SwcNode;
|
parent: SwcNode;
|
||||||
stop: function;
|
stop: function;
|
||||||
scope: SwcScope;
|
scope: SwcScope | undefined;
|
||||||
parentPath: SwcPath;
|
parentPath: SwcPath | null | undefined;
|
||||||
|
get: (id: string) => SwcPath | undefined;
|
||||||
|
type: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SwcVisitorFn = (swcPath: SwcPath) => void;
|
type SwcVisitorFn = (swcPath: SwcPath) => void;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue