lion/packages-node/providence-analytics/test-node/program/analyzers/find-exports.test.js

331 lines
14 KiB
JavaScript

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.deep.equal(['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.deep.equal(['[default]']);
expect(firstResult.source).to.be.undefined;
});
it(`supports "export default x => x * 3;" (default function 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.deep.equal(['[default]']);
expect(firstResult.source).to.be.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.deep.equal({
exportSpecifiers: ['[default]'],
source: undefined,
rootFileMap: [
{
currentFileSpecifier: '[default]',
rootFile: { file: '[current]', specifier: '[default]' },
},
],
});
const secondEntry = getEntry(queryResults[0], 1);
expect(secondEntry.result[0]).to.deep.equal({
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.deep.equal({
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.deep.equal({
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.deep.equal([
{
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.deep.equal(['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.deep.equal([
{
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.deep.equal([
{
currentFileSpecifier: 'OriginalComp',
rootFile: {
file: '[current]',
specifier: 'OriginalComp',
},
},
]);
expect(thirdEntry.result[0].rootFileMap).to.deep.equal([
{
currentFileSpecifier: 'InBetweenComp',
rootFile: {
file: './src/OriginalComp.js',
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.deep.equal([
{
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.deep.equal(['[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.deep.equal(['fooCategory']);
// not mutually exclusive...
expect(secondEntry.meta.categories).to.deep.equal(['barCategory', 'testCategory']);
expect(thirdEntry.meta.categories).to.deep.equal([]);
});
});
});