feat(providence-analytics): improvements find-exports, trackdown-identifier, get-source-code-fragment-of-declaration
This commit is contained in:
parent
dd3458af70
commit
f7fc7df349
7 changed files with 137 additions and 9 deletions
5
.changeset/great-jokes-travel.md
Normal file
5
.changeset/great-jokes-travel.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'providence-analytics': patch
|
||||
---
|
||||
|
||||
improvements find-exports, trackdown-identifier, get-source-code-fragment-of-declaration
|
||||
|
|
@ -46,16 +46,19 @@ async function trackdownRoot(transformedEntry, relativePath, projectPath) {
|
|||
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,
|
||||
|
|
@ -122,7 +125,8 @@ function getLocalNameSpecifiers(node) {
|
|||
.map(s => {
|
||||
if (s.exported && s.local && s.exported.name !== s.local.name) {
|
||||
return {
|
||||
local: s.local.name,
|
||||
// if reserved keyword 'default' is used, translate it into 'providence keyword'
|
||||
local: s.local.name === 'default' ? '[default]' : s.local.name,
|
||||
exported: s.exported.name,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ async function trackDownIdentifierFn(source, identifierName, currentFilePath, ro
|
|||
|
||||
let reexportMatch = false; // named specifier declaration
|
||||
let pendingTrackDownPromise;
|
||||
let exportMatch;
|
||||
|
||||
traverse(ast, {
|
||||
ExportDefaultDeclaration(path) {
|
||||
|
|
@ -172,7 +173,7 @@ async function trackDownIdentifierFn(source, identifierName, currentFilePath, ro
|
|||
}
|
||||
// Are we dealing with a re-export ?
|
||||
if (path.node.specifiers && path.node.specifiers.length) {
|
||||
const exportMatch = path.node.specifiers.find(s => s.exported.name === identifierName);
|
||||
exportMatch = path.node.specifiers.find(s => s.exported.name === identifierName);
|
||||
|
||||
if (exportMatch) {
|
||||
const localName = exportMatch.local.name;
|
||||
|
|
@ -218,7 +219,10 @@ async function trackDownIdentifierFn(source, identifierName, currentFilePath, ro
|
|||
// in current file...
|
||||
rootSpecifier = identifierName;
|
||||
rootFilePath = toRelativeSourcePath(resolvedSourcePath, rootPath);
|
||||
path.stop();
|
||||
|
||||
if (exportMatch) {
|
||||
path.stop();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,16 @@ const path = require('path');
|
|||
const babelTraversePkg = require('@babel/traverse');
|
||||
const { AstService } = require('../services/AstService.js');
|
||||
const { trackDownIdentifier } = require('../analyzers/helpers/track-down-identifier.js');
|
||||
const { toPosixPath } = require('./to-posix-path.js');
|
||||
|
||||
function getFilePathOrExternalSource({ rootPath, localPath }) {
|
||||
if (!localPath.startsWith('.')) {
|
||||
// We are not resolving external files like '@lion/input-amount/x.js',
|
||||
// but we give a 100% score if from and to are same here..
|
||||
return localPath;
|
||||
}
|
||||
return toPosixPath(path.resolve(rootPath, localPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assume we had:
|
||||
|
|
@ -82,13 +92,15 @@ async function getSourceCodeFragmentOfDeclaration({
|
|||
}
|
||||
} else {
|
||||
const variableDeclaratorPath = babelPath.scope.getBinding(exportedIdentifier).path;
|
||||
const isReferenced = variableDeclaratorPath.node.init?.type === 'Identifier';
|
||||
const contentPath = variableDeclaratorPath.node.init
|
||||
const varDeclNode = variableDeclaratorPath.node;
|
||||
const isReferenced = varDeclNode.init?.type === 'Identifier';
|
||||
const contentPath = varDeclNode.init
|
||||
? variableDeclaratorPath.get('init')
|
||||
: variableDeclaratorPath;
|
||||
const name = variableDeclaratorPath.node.init
|
||||
? variableDeclaratorPath.node.init.name
|
||||
: variableDeclaratorPath.node.id.name;
|
||||
|
||||
const name = varDeclNode.init
|
||||
? varDeclNode.init.name
|
||||
: varDeclNode.id?.name || varDeclNode.imported.name;
|
||||
|
||||
if (!isReferenced) {
|
||||
// it must be an exported declaration
|
||||
|
|
@ -115,19 +127,36 @@ async function getSourceCodeFragmentOfDeclaration({
|
|||
currentFilePath,
|
||||
projectRootPath,
|
||||
);
|
||||
const filePathOrSrc = getFilePathOrExternalSource({
|
||||
rootPath: projectRootPath,
|
||||
localPath: rootFile.file,
|
||||
});
|
||||
|
||||
// TODO: allow resolving external project file paths
|
||||
if (!filePathOrSrc.startsWith('/')) {
|
||||
// So we have external project; smth like '@lion/input/x.js'
|
||||
return {
|
||||
sourceNodePath: finalNodePath,
|
||||
sourceFragment: null,
|
||||
externalImportSource: filePathOrSrc,
|
||||
};
|
||||
}
|
||||
|
||||
return getSourceCodeFragmentOfDeclaration({
|
||||
filePath: path.resolve(projectRootPath, rootFile.file),
|
||||
filePath: filePathOrSrc,
|
||||
exportedIdentifier: rootFile.specifier,
|
||||
projectRootPath,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
sourceNodePath: finalNodePath,
|
||||
sourceFragment: code.slice(finalNodePath.node?.start, finalNodePath.node?.end),
|
||||
externalImportSource: null,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getSourceCodeFragmentOfDeclaration,
|
||||
getFilePathOrExternalSource,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
const {
|
||||
getSourceCodeFragmentOfDeclaration,
|
||||
getFilePathOrExternalSource,
|
||||
} = require('./get-source-code-fragment-of-declaration.js');
|
||||
|
||||
// TODO: move trackdownIdentifier to utils as well
|
||||
|
||||
module.exports = {
|
||||
getSourceCodeFragmentOfDeclaration,
|
||||
getFilePathOrExternalSource,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -66,6 +66,45 @@ describe('Analyzer "find-exports"', () => {
|
|||
expect(firstEntry.result[0].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';",
|
||||
});
|
||||
|
||||
await providence(findExportsQueryConfig, _providenceCfg);
|
||||
const queryResult = queryResults[0];
|
||||
const firstEntry = getEntry(queryResult);
|
||||
expect(firstEntry.result[0]).to.eql({
|
||||
exportSpecifiers: ['[default]'],
|
||||
source: undefined,
|
||||
rootFileMap: [
|
||||
{
|
||||
currentFileSpecifier: '[default]',
|
||||
rootFile: { file: '[current]', specifier: '[default]' },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const secondEntry = getEntry(queryResult, 1);
|
||||
expect(secondEntry.result[0].exportSpecifiers.length).to.equal(1);
|
||||
expect(secondEntry.result[0].exportSpecifiers[0]).to.equal('namedExport');
|
||||
expect(secondEntry.result[0].source).to.equal('./file-with-default-export.js');
|
||||
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 [export { x } from 'my/source'] (re-export named specifier)`, async () => {
|
||||
mockProject([`export { x } from 'my/source'`]);
|
||||
await providence(findExportsQueryConfig, _providenceCfg);
|
||||
|
|
|
|||
|
|
@ -168,6 +168,51 @@ describe('trackdownIdentifier', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it(`works with multiple re-exports in a file`, async () => {
|
||||
mockProject(
|
||||
{
|
||||
'./packages/accordion/IngAccordionContent.js': `export class IngAccordionContent { }`,
|
||||
'./packages/accordion/IngAccordionInvokerButton.js': `export class IngAccordionInvokerButton { }`,
|
||||
'./packages/accordion/index.js': `
|
||||
export { IngAccordionContent } from './IngAccordionContent.js';
|
||||
export { IngAccordionInvokerButton } from './IngAccordionInvokerButton.js';`,
|
||||
},
|
||||
{
|
||||
projectName: 'my-project',
|
||||
projectPath: '/my/project',
|
||||
},
|
||||
);
|
||||
|
||||
// Let's say we want to track down 'IngAccordionInvokerButton' in the code above
|
||||
const source = './IngAccordionContent.js';
|
||||
const identifierName = 'IngAccordionContent';
|
||||
const currentFilePath = '/my/project/packages/accordion/index.js';
|
||||
const rootPath = '/my/project';
|
||||
|
||||
const rootFile = await trackDownIdentifier(source, identifierName, currentFilePath, rootPath);
|
||||
expect(rootFile).to.eql({
|
||||
file: './packages/accordion/IngAccordionContent.js',
|
||||
specifier: 'IngAccordionContent',
|
||||
});
|
||||
|
||||
// Let's say we want to track down 'IngAccordionInvokerButton' in the code above
|
||||
const source2 = './IngAccordionInvokerButton.js';
|
||||
const identifierName2 = 'IngAccordionInvokerButton';
|
||||
const currentFilePath2 = '/my/project/packages/accordion/index.js';
|
||||
const rootPath2 = '/my/project';
|
||||
|
||||
const rootFile2 = await trackDownIdentifier(
|
||||
source2,
|
||||
identifierName2,
|
||||
currentFilePath2,
|
||||
rootPath2,
|
||||
);
|
||||
expect(rootFile2).to.eql({
|
||||
file: './packages/accordion/IngAccordionInvokerButton.js',
|
||||
specifier: 'IngAccordionInvokerButton',
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: improve perf
|
||||
describe.skip('Caching', () => {});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue