feat(providence-analytics): support import assertions
This commit is contained in:
parent
1f87043e82
commit
8890cc0dea
8 changed files with 101 additions and 14 deletions
|
|
@ -33,6 +33,7 @@
|
||||||
"@babel/parser": "^7.5.5",
|
"@babel/parser": "^7.5.5",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||||
"@babel/plugin-syntax-export-default-from": "^7.18.6",
|
"@babel/plugin-syntax-export-default-from": "^7.18.6",
|
||||||
|
"@babel/plugin-syntax-import-assertions": "^7.18.6",
|
||||||
"@babel/register": "^7.5.5",
|
"@babel/register": "^7.5.5",
|
||||||
"@babel/traverse": "^7.5.5",
|
"@babel/traverse": "^7.5.5",
|
||||||
"@babel/types": "^7.9.0",
|
"@babel/types": "^7.9.0",
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,11 @@ function findExportsPerAstEntry(ast, { skipFileImports }) {
|
||||||
const exportSpecifiers = getExportSpecifiers(path.node);
|
const exportSpecifiers = getExportSpecifiers(path.node);
|
||||||
const localMap = getLocalNameSpecifiers(path.node);
|
const localMap = getLocalNameSpecifiers(path.node);
|
||||||
const source = path.node.source?.value;
|
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) {
|
ExportDefaultDeclaration(defaultExportPath) {
|
||||||
const exportSpecifiers = ['[default]'];
|
const exportSpecifiers = ['[default]'];
|
||||||
|
|
|
||||||
|
|
@ -60,11 +60,15 @@ function findImportsPerAstEntry(ast) {
|
||||||
traverse(ast, {
|
traverse(ast, {
|
||||||
ImportDeclaration(path) {
|
ImportDeclaration(path) {
|
||||||
const importSpecifiers = getImportOrReexportsSpecifiers(path.node);
|
const importSpecifiers = getImportOrReexportsSpecifiers(path.node);
|
||||||
if (importSpecifiers.length === 0) {
|
if (!importSpecifiers.length) {
|
||||||
importSpecifiers.push('[file]'); // apparently, there was just a file import
|
importSpecifiers.push('[file]'); // apparently, there was just a file import
|
||||||
}
|
}
|
||||||
const source = path.node.source.value;
|
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
|
// Dynamic imports
|
||||||
CallExpression(path) {
|
CallExpression(path) {
|
||||||
|
|
@ -86,7 +90,11 @@ function findImportsPerAstEntry(ast) {
|
||||||
}
|
}
|
||||||
const importSpecifiers = getImportOrReexportsSpecifiers(path.node);
|
const importSpecifiers = getImportOrReexportsSpecifiers(path.node);
|
||||||
const source = path.node.source.value;
|
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) {
|
// ExportAllDeclaration(path) {
|
||||||
// if (!path.node.source) {
|
// if (!path.node.source) {
|
||||||
|
|
@ -97,6 +105,7 @@ function findImportsPerAstEntry(ast) {
|
||||||
// transformedEntry.push({ importSpecifiers, source });
|
// transformedEntry.push({ importSpecifiers, source });
|
||||||
// },
|
// },
|
||||||
});
|
});
|
||||||
|
|
||||||
return transformedEntry;
|
return transformedEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,15 +149,16 @@ class FindImportsAnalyzer extends Analyzer {
|
||||||
const queryOutput = await this._traverse(async (ast, { relativePath }) => {
|
const queryOutput = await this._traverse(async (ast, { relativePath }) => {
|
||||||
let transformedEntry = findImportsPerAstEntry(ast);
|
let transformedEntry = findImportsPerAstEntry(ast);
|
||||||
// Post processing based on configuration...
|
// Post processing based on configuration...
|
||||||
|
|
||||||
transformedEntry = await normalizeSourcePaths(
|
transformedEntry = await normalizeSourcePaths(
|
||||||
transformedEntry,
|
transformedEntry,
|
||||||
relativePath,
|
relativePath,
|
||||||
cfg.targetProjectPath,
|
cfg.targetProjectPath,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!cfg.keepInternalSources) {
|
if (!cfg.keepInternalSources) {
|
||||||
transformedEntry = options.onlyExternalSources(transformedEntry);
|
transformedEntry = options.onlyExternalSources(transformedEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { result: transformedEntry };
|
return { result: transformedEntry };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const pathLib = require('path');
|
||||||
const { default: traverse } = require('@babel/traverse');
|
const { default: traverse } = require('@babel/traverse');
|
||||||
const {
|
const {
|
||||||
isRelativeSourcePath,
|
isRelativeSourcePath,
|
||||||
|
|
@ -157,7 +158,16 @@ async function trackDownIdentifierFn(
|
||||||
* @type {PathFromSystemRoot}
|
* @type {PathFromSystemRoot}
|
||||||
*/
|
*/
|
||||||
const resolvedSourcePath = await resolveImportPath(source, currentFilePath);
|
const resolvedSourcePath = await resolveImportPath(source, currentFilePath);
|
||||||
|
|
||||||
LogService.debug(`[trackDownIdentifier] ${resolvedSourcePath}`);
|
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 code = fs.readFileSync(resolvedSourcePath, 'utf8');
|
||||||
const ast = AstService.getAst(code, 'babel', { filePath: resolvedSourcePath });
|
const ast = AstService.getAst(code, 'babel', { filePath: resolvedSourcePath });
|
||||||
const shouldLookForDefaultExport = identifierName === '[default]';
|
const shouldLookForDefaultExport = identifierName === '[default]';
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,13 @@ class AstService {
|
||||||
static _getBabelAst(code) {
|
static _getBabelAst(code) {
|
||||||
const ast = babelParser.parse(code, {
|
const ast = babelParser.parse(code, {
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
plugins: ['importMeta', 'dynamicImport', 'classProperties', 'exportDefaultFrom'],
|
plugins: [
|
||||||
|
'importMeta',
|
||||||
|
'dynamicImport',
|
||||||
|
'classProperties',
|
||||||
|
'exportDefaultFrom',
|
||||||
|
'importAssertions',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
return ast;
|
return ast;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,46 @@ describe('Analyzer "find-exports"', () => {
|
||||||
expect(firstEntry.result[0].source).to.equal('my/source');
|
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 () => {
|
it(`stores meta info(local name) of renamed specifiers`, async () => {
|
||||||
mockProject([`export { x as y } from 'my/source'`]);
|
mockProject([`export { x as y } from 'my/source'`]);
|
||||||
await providence(findExportsQueryConfig, _providenceCfg);
|
await providence(findExportsQueryConfig, _providenceCfg);
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,7 @@ describe('Analyzer "find-imports"', () => {
|
||||||
expect(firstEntry.result[0].source).to.equal('my/source');
|
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 () => {
|
it(`supports [import(pathReference)] (dynamic imports with variable source)`, async () => {
|
||||||
mockProject([
|
mockProject([
|
||||||
`
|
`
|
||||||
|
|
@ -183,6 +184,29 @@ describe('Analyzer "find-imports"', () => {
|
||||||
expect(firstEntry.result[0].source).to.equal('[variable]');
|
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', () => {
|
describe('Filter out false positives', () => {
|
||||||
it(`doesn't support [object.import('my/source')] (import method members)`, async () => {
|
it(`doesn't support [object.import('my/source')] (import method members)`, async () => {
|
||||||
mockProject([`object.import('my/source')`]);
|
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 <script type="module"> inside .html', () => {
|
|
||||||
it('gets the source from script tags', async () => {});
|
|
||||||
|
|
||||||
it('gets the content from script tags', async () => {});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,6 @@ describe('trackdownIdentifier', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: support import maps
|
|
||||||
it(`identifies import map entries as internal sources`, async () => {
|
it(`identifies import map entries as internal sources`, async () => {
|
||||||
mockProject(
|
mockProject(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue