From 8890cc0dea16c168485c1edebb1164a8f5ac2b55 Mon Sep 17 00:00:00 2001 From: Thijs Louisse Date: Tue, 27 Sep 2022 14:32:55 +0200 Subject: [PATCH] feat(providence-analytics): support import assertions --- .../providence-analytics/package.json | 1 + .../src/program/analyzers/find-exports.js | 6 ++- .../src/program/analyzers/find-imports.js | 18 +++++++-- .../helpers/track-down-identifier.js | 10 +++++ .../src/program/services/AstService.js | 8 +++- .../program/analyzers/find-exports.test.js | 40 +++++++++++++++++++ .../program/analyzers/find-imports.test.js | 31 ++++++++++---- .../helpers/track-down-identifier.test.js | 1 - 8 files changed, 101 insertions(+), 14 deletions(-) diff --git a/packages-node/providence-analytics/package.json b/packages-node/providence-analytics/package.json index 8101b8278..c02f5aba2 100644 --- a/packages-node/providence-analytics/package.json +++ b/packages-node/providence-analytics/package.json @@ -33,6 +33,7 @@ "@babel/parser": "^7.5.5", "@babel/plugin-proposal-class-properties": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.18.6", + "@babel/plugin-syntax-import-assertions": "^7.18.6", "@babel/register": "^7.5.5", "@babel/traverse": "^7.5.5", "@babel/types": "^7.9.0", diff --git a/packages-node/providence-analytics/src/program/analyzers/find-exports.js b/packages-node/providence-analytics/src/program/analyzers/find-exports.js index 0abdd4363..da9db9d6c 100644 --- a/packages-node/providence-analytics/src/program/analyzers/find-exports.js +++ b/packages-node/providence-analytics/src/program/analyzers/find-exports.js @@ -170,7 +170,11 @@ function findExportsPerAstEntry(ast, { skipFileImports }) { const exportSpecifiers = getExportSpecifiers(path.node); const localMap = getLocalNameSpecifiers(path.node); const source = path.node.source?.value; - transformedEntry.push({ exportSpecifiers, localMap, source, __tmp: { path } }); + const entry = { exportSpecifiers, localMap, source, __tmp: { path } }; + if (path.node.assertions?.length) { + entry.assertionType = path.node.assertions[0].value?.value; + } + transformedEntry.push(entry); }, ExportDefaultDeclaration(defaultExportPath) { const exportSpecifiers = ['[default]']; 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 d1f89c3c2..9d179efc0 100644 --- a/packages-node/providence-analytics/src/program/analyzers/find-imports.js +++ b/packages-node/providence-analytics/src/program/analyzers/find-imports.js @@ -60,11 +60,15 @@ function findImportsPerAstEntry(ast) { traverse(ast, { ImportDeclaration(path) { const importSpecifiers = getImportOrReexportsSpecifiers(path.node); - if (importSpecifiers.length === 0) { + if (!importSpecifiers.length) { importSpecifiers.push('[file]'); // apparently, there was just a file import } const source = path.node.source.value; - transformedEntry.push({ importSpecifiers, source }); + const entry = { importSpecifiers, source }; + if (path.node.assertions?.length) { + entry.assertionType = path.node.assertions[0].value?.value; + } + transformedEntry.push(entry); }, // Dynamic imports CallExpression(path) { @@ -86,7 +90,11 @@ function findImportsPerAstEntry(ast) { } const importSpecifiers = getImportOrReexportsSpecifiers(path.node); const source = path.node.source.value; - transformedEntry.push({ importSpecifiers, source }); + const entry = { importSpecifiers, source }; + if (path.node.assertions?.length) { + entry.assertionType = path.node.assertions[0].value?.value; + } + transformedEntry.push(entry); }, // ExportAllDeclaration(path) { // if (!path.node.source) { @@ -97,6 +105,7 @@ function findImportsPerAstEntry(ast) { // transformedEntry.push({ importSpecifiers, source }); // }, }); + return transformedEntry; } @@ -140,15 +149,16 @@ class FindImportsAnalyzer extends Analyzer { const queryOutput = await this._traverse(async (ast, { relativePath }) => { let transformedEntry = findImportsPerAstEntry(ast); // Post processing based on configuration... - transformedEntry = await normalizeSourcePaths( transformedEntry, relativePath, cfg.targetProjectPath, ); + if (!cfg.keepInternalSources) { transformedEntry = options.onlyExternalSources(transformedEntry); } + return { result: transformedEntry }; }); 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 23cb449c0..2b5e7aba5 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 @@ -1,4 +1,5 @@ const fs = require('fs'); +const pathLib = require('path'); const { default: traverse } = require('@babel/traverse'); const { isRelativeSourcePath, @@ -157,7 +158,16 @@ async function trackDownIdentifierFn( * @type {PathFromSystemRoot} */ const resolvedSourcePath = await resolveImportPath(source, currentFilePath); + LogService.debug(`[trackDownIdentifier] ${resolvedSourcePath}`); + const allowedJsModuleExtensions = ['.mjs', '.js']; + if (!allowedJsModuleExtensions.includes(pathLib.extname(resolvedSourcePath))) { + // We have an import assertion + return /** @type { RootFile } */ { + file: toRelativeSourcePath(resolvedSourcePath, rootPath), + specifier: '[default]', + }; + } const code = fs.readFileSync(resolvedSourcePath, 'utf8'); const ast = AstService.getAst(code, 'babel', { filePath: resolvedSourcePath }); const shouldLookForDefaultExport = identifierName === '[default]'; diff --git a/packages-node/providence-analytics/src/program/services/AstService.js b/packages-node/providence-analytics/src/program/services/AstService.js index 51a3150d9..1fa5b5c12 100644 --- a/packages-node/providence-analytics/src/program/services/AstService.js +++ b/packages-node/providence-analytics/src/program/services/AstService.js @@ -61,7 +61,13 @@ class AstService { static _getBabelAst(code) { const ast = babelParser.parse(code, { sourceType: 'module', - plugins: ['importMeta', 'dynamicImport', 'classProperties', 'exportDefaultFrom'], + plugins: [ + 'importMeta', + 'dynamicImport', + 'classProperties', + 'exportDefaultFrom', + 'importAssertions', + ], }); return ast; } 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 316693349..136873a35 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 @@ -155,6 +155,46 @@ describe('Analyzer "find-exports"', () => { 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 from './styles.css' assert { type: "css" };`, + }); + await providence(findExportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); + expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('styles'); + expect(firstEntry.result[0].source).to.equal('./styles.css'); + expect(firstEntry.result[0].rootFileMap[0]).to.eql({ + currentFileSpecifier: 'styles', + 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;`, + }); + await providence(findExportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + 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'`]); await providence(findExportsQueryConfig, _providenceCfg); diff --git a/packages-node/providence-analytics/test-node/program/analyzers/find-imports.test.js b/packages-node/providence-analytics/test-node/program/analyzers/find-imports.test.js index c5f7b4d32..3cad4b376 100644 --- a/packages-node/providence-analytics/test-node/program/analyzers/find-imports.test.js +++ b/packages-node/providence-analytics/test-node/program/analyzers/find-imports.test.js @@ -168,6 +168,7 @@ describe('Analyzer "find-imports"', () => { expect(firstEntry.result[0].source).to.equal('my/source'); }); + // TODO: we can track [variable] down via trackdownId + getSourceCodeFragmentOfDeclaration it(`supports [import(pathReference)] (dynamic imports with variable source)`, async () => { mockProject([ ` @@ -183,6 +184,29 @@ describe('Analyzer "find-imports"', () => { expect(firstEntry.result[0].source).to.equal('[variable]'); }); + // import styles from "./styles.css" assert { type: "css" }; + it(`supports [import styles from "@css/lib/styles.css" assert { type: "css" }] (import assertions)`, async () => { + mockProject([`import styles from "@css/lib/styles.css" assert { type: "css" };`]); + await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + console.log({ firstEntry }); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal('@css/lib/styles.css'); + expect(firstEntry.result[0].assertionType).to.equal('css'); + }); + + it(`supports [export styles from "@css/lib/styles.css" assert { type: "css" }] (import assertions)`, async () => { + mockProject([`export styles from "@css/lib/styles.css" assert { type: "css" };`]); + await providence(findImportsQueryConfig, _providenceCfg); + const queryResult = queryResults[0]; + const firstEntry = getEntry(queryResult); + console.log({ firstEntry }); + expect(firstEntry.result[0].importSpecifiers[0]).to.equal('[default]'); + expect(firstEntry.result[0].source).to.equal('@css/lib/styles.css'); + expect(firstEntry.result[0].assertionType).to.equal('css'); + }); + describe('Filter out false positives', () => { it(`doesn't support [object.import('my/source')] (import method members)`, async () => { mockProject([`object.import('my/source')`]); @@ -336,11 +360,4 @@ describe('Analyzer "find-imports"', () => { ]); }); }); - - // TODO: put this in the generic providence/analyzer part - describe.skip('With