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 8d28f1323..674d7cf19 100644 --- a/packages-node/providence-analytics/src/program/utils/get-source-code-fragment-of-declaration.js +++ b/packages-node/providence-analytics/src/program/utils/get-source-code-fragment-of-declaration.js @@ -1,6 +1,8 @@ const fs = require('fs'); +const path = require('path'); const babelTraversePkg = require('@babel/traverse'); const { AstService } = require('../services/AstService.js'); +const { trackDownIdentifier } = require('../analyzers/helpers/track-down-identifier.js'); /** * Assume we had: @@ -22,6 +24,10 @@ function getReferencedDeclaration({ referencedIdentifierName, globalScopeBinding ([key]) => key === referencedIdentifierName, ); + if (refDeclaratorBinding.path.type === 'ImportSpecifier') { + return refDeclaratorBinding.path; + } + if (refDeclaratorBinding.path.node.init.type === 'Identifier') { return getReferencedDeclaration({ referencedIdentifierName: refDeclaratorBinding.path.node.init.name, @@ -36,7 +42,11 @@ function getReferencedDeclaration({ referencedIdentifierName, globalScopeBinding * * @param {{ filePath: string; exportedIdentifier: string; }} opts */ -async function getSourceCodeFragmentOfDeclaration({ filePath, exportedIdentifier }) { +async function getSourceCodeFragmentOfDeclaration({ + filePath, + exportedIdentifier, + projectRootPath, +}) { const code = fs.readFileSync(filePath, 'utf-8'); const ast = AstService.getAst(code, 'babel'); @@ -73,17 +83,19 @@ async function getSourceCodeFragmentOfDeclaration({ filePath, exportedIdentifier } else { const variableDeclaratorPath = babelPath.scope.getBinding(exportedIdentifier).path; const isReferenced = variableDeclaratorPath.node.init?.type === 'Identifier'; + const contentPath = variableDeclaratorPath.node.init + ? variableDeclaratorPath.get('init') + : variableDeclaratorPath; + const name = variableDeclaratorPath.node.init + ? variableDeclaratorPath.node.init.name + : variableDeclaratorPath.node.id.name; if (!isReferenced) { // it must be an exported declaration - finalNodePath = variableDeclaratorPath.node.init - ? variableDeclaratorPath.get('init') - : variableDeclaratorPath; + finalNodePath = contentPath; } else { finalNodePath = getReferencedDeclaration({ - referencedIdentifierName: variableDeclaratorPath.node.init - ? variableDeclaratorPath.node.init.name - : variableDeclaratorPath.node.id.name, + referencedIdentifierName: name, globalScopeBindings, }); } @@ -91,9 +103,28 @@ async function getSourceCodeFragmentOfDeclaration({ filePath, exportedIdentifier }, }); + if (finalNodePath.type === 'ImportSpecifier') { + const importDeclNode = finalNodePath.parentPath.node; + const source = importDeclNode.source.value; + const identifierName = finalNodePath.node.imported.name; + const currentFilePath = filePath; + + const rootFile = await trackDownIdentifier( + source, + identifierName, + currentFilePath, + projectRootPath, + ); + + return getSourceCodeFragmentOfDeclaration({ + filePath: path.resolve(projectRootPath, rootFile.file), + exportedIdentifier: rootFile.specifier, + }); + } + return { sourceNodePath: finalNodePath, - sourceFragment: code.slice(finalNodePath.node.start, finalNodePath.node.end), + sourceFragment: code.slice(finalNodePath.node?.start, finalNodePath.node?.end), }; } diff --git a/packages-node/providence-analytics/test-node/program/utils/getSourceCodeFragmentOfDeclaration.test.js b/packages-node/providence-analytics/test-node/program/utils/getSourceCodeFragmentOfDeclaration.test.js index c13989017..d92bce16b 100644 --- a/packages-node/providence-analytics/test-node/program/utils/getSourceCodeFragmentOfDeclaration.test.js +++ b/packages-node/providence-analytics/test-node/program/utils/getSourceCodeFragmentOfDeclaration.test.js @@ -53,6 +53,27 @@ describe('getSourceCodeFragmentOfDeclaration', () => { expect(sourceFragment).to.equal('88'); }); + it('finds source code for imported referenced specifiers', async () => { + const fakeFs = { + '/my/proj/exports/file-1.js': ` + export const black59 = '#aaa'; + `, + '/my/proj/exports/file-2.js': ` + import { black59 } from './file-1.js'; + export const black67 = black59; + `, + }; + mock(fakeFs); + + const { sourceFragment } = await getSourceCodeFragmentOfDeclaration({ + filePath: '/my/proj/exports/file-2.js', + exportedIdentifier: 'black67', + projectRootPath: '/my/proj', + }); + + expect(sourceFragment).to.equal("'#aaa'"); + }); + describe('Different types of declarations', () => { it('handles class declarations', async () => { const fakeFs = {