feat(providence-analytics): support project import-maps and self refs
This commit is contained in:
parent
07a95a1a67
commit
1f87043e82
3 changed files with 105 additions and 6 deletions
|
|
@ -4,9 +4,10 @@ const {
|
|||
isRelativeSourcePath,
|
||||
toRelativeSourcePath,
|
||||
} = require('../../utils/relative-source-path.js');
|
||||
const { resolveImportPath } = require('../../utils/resolve-import-path.js');
|
||||
const { AstService } = require('../../services/AstService.js');
|
||||
const { LogService } = require('../../services/LogService.js');
|
||||
const { InputDataService } = require('../../services/InputDataService.js');
|
||||
const { resolveImportPath } = require('../../utils/resolve-import-path.js');
|
||||
const { memoize } = require('../../utils/memoize.js');
|
||||
|
||||
/**
|
||||
|
|
@ -16,6 +17,18 @@ const { memoize } = require('../../utils/memoize.js');
|
|||
* @typedef {import('../../types/core').PathFromSystemRoot} PathFromSystemRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @param {string} projectName
|
||||
*/
|
||||
function isExternalProject(source, projectName) {
|
||||
return (
|
||||
!source.startsWith('#') &&
|
||||
!isRelativeSourcePath(source) &&
|
||||
!source.startsWith(`${projectName}/`)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Other than with import, no binding is created for MyClass by Babel(?)
|
||||
* This means 'path.scope.getBinding('MyClass')' returns undefined
|
||||
|
|
@ -108,14 +121,27 @@ let trackDownIdentifier;
|
|||
* @param {IdentifierName} identifierName imported reference/Identifier name, like 'MyComp'
|
||||
* @param {PathFromSystemRoot} currentFilePath file path, like '/path/to/target-proj/my-comp-import.js'
|
||||
* @param {PathFromSystemRoot} rootPath dir path, like '/path/to/target-proj'
|
||||
* @param {string} [projectName] like 'target-proj' or '@lion/input'
|
||||
* @returns {Promise<RootFile>} file: path of file containing the binding (exported declaration),
|
||||
* like '/path/to/ref-proj/src/RefComp.js'
|
||||
*/
|
||||
async function trackDownIdentifierFn(source, identifierName, currentFilePath, rootPath, depth = 0) {
|
||||
async function trackDownIdentifierFn(
|
||||
source,
|
||||
identifierName,
|
||||
currentFilePath,
|
||||
rootPath,
|
||||
projectName,
|
||||
depth = 0,
|
||||
) {
|
||||
let rootFilePath; // our result path
|
||||
let rootSpecifier; // the name under which it was imported
|
||||
|
||||
if (!isRelativeSourcePath(source)) {
|
||||
if (!projectName) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
projectName = InputDataService.getPackageJson(rootPath)?.name;
|
||||
}
|
||||
|
||||
if (isExternalProject(source, projectName)) {
|
||||
// So, it is an external ref like '@lion/core' or '@open-wc/scoped-elements/index.js'
|
||||
// At this moment in time, we don't know if we have file system access to this particular
|
||||
// project. Therefore, we limit ourselves to tracking down local references.
|
||||
|
|
@ -137,8 +163,8 @@ async function trackDownIdentifierFn(source, identifierName, currentFilePath, ro
|
|||
const shouldLookForDefaultExport = identifierName === '[default]';
|
||||
|
||||
let reexportMatch = false; // named specifier declaration
|
||||
let pendingTrackDownPromise;
|
||||
let exportMatch;
|
||||
let pendingTrackDownPromise;
|
||||
|
||||
traverse(ast, {
|
||||
ExportDefaultDeclaration(path) {
|
||||
|
|
@ -157,6 +183,7 @@ async function trackDownIdentifierFn(source, identifierName, currentFilePath, ro
|
|||
'[default]',
|
||||
resolvedSourcePath,
|
||||
rootPath,
|
||||
projectName,
|
||||
depth + 1,
|
||||
);
|
||||
} else {
|
||||
|
|
@ -173,7 +200,7 @@ async function trackDownIdentifierFn(source, identifierName, currentFilePath, ro
|
|||
}
|
||||
|
||||
// Are we dealing with a re-export ?
|
||||
if (path.node.specifiers && path.node.specifiers.length) {
|
||||
if (path.node.specifiers?.length) {
|
||||
exportMatch = path.node.specifiers.find(s => s.exported.name === identifierName);
|
||||
|
||||
if (exportMatch) {
|
||||
|
|
@ -203,12 +230,12 @@ async function trackDownIdentifierFn(source, identifierName, currentFilePath, ro
|
|||
}
|
||||
}
|
||||
reexportMatch = true;
|
||||
|
||||
pendingTrackDownPromise = trackDownIdentifier(
|
||||
newSource,
|
||||
localName,
|
||||
resolvedSourcePath,
|
||||
rootPath,
|
||||
projectName,
|
||||
depth + 1,
|
||||
);
|
||||
path.stop();
|
||||
|
|
@ -236,6 +263,7 @@ async function trackDownIdentifierFn(source, identifierName, currentFilePath, ro
|
|||
rootFilePath = resObj.file;
|
||||
rootSpecifier = resObj.specifier;
|
||||
}
|
||||
|
||||
return /** @type { RootFile } */ { file: rootFilePath, specifier: rootSpecifier };
|
||||
}
|
||||
|
||||
|
|
@ -246,12 +274,14 @@ trackDownIdentifier = memoize(trackDownIdentifierFn);
|
|||
* @param {string} identifierNameInScope
|
||||
* @param {string} fullCurrentFilePath
|
||||
* @param {string} projectPath
|
||||
* @param {string} [projectName]
|
||||
*/
|
||||
async function trackDownIdentifierFromScopeFn(
|
||||
astPath,
|
||||
identifierNameInScope,
|
||||
fullCurrentFilePath,
|
||||
projectPath,
|
||||
projectName,
|
||||
) {
|
||||
const sourceObj = getImportSourceFromAst(astPath, identifierNameInScope);
|
||||
|
||||
|
|
@ -263,6 +293,7 @@ async function trackDownIdentifierFromScopeFn(
|
|||
sourceObj.importedIdentifierName,
|
||||
fullCurrentFilePath,
|
||||
projectPath,
|
||||
projectName,
|
||||
);
|
||||
} else {
|
||||
const specifier = sourceObj.importedIdentifierName || identifierNameInScope;
|
||||
|
|
|
|||
|
|
@ -690,4 +690,6 @@ class InputDataService {
|
|||
}
|
||||
InputDataService.cacheDisabled = false;
|
||||
|
||||
InputDataService.getPackageJson = getPackageJson;
|
||||
|
||||
module.exports = { InputDataService };
|
||||
|
|
|
|||
|
|
@ -137,6 +137,72 @@ describe('trackdownIdentifier', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// TODO: support import maps
|
||||
it(`identifies import map entries as internal sources`, async () => {
|
||||
mockProject(
|
||||
{
|
||||
'./MyClass.js': `export default class {}`,
|
||||
'./currentFile.js': `
|
||||
import MyClass from '#internal/source';
|
||||
`,
|
||||
'./package.json': JSON.stringify({
|
||||
name: 'my-project',
|
||||
imports: { '#internal/source': './MyClass.js' },
|
||||
}),
|
||||
},
|
||||
{
|
||||
projectName: 'my-project',
|
||||
projectPath: '/my/project',
|
||||
},
|
||||
);
|
||||
|
||||
// Let's say we want to track down 'MyClass' in the code above
|
||||
const source = '#internal/source';
|
||||
const identifierName = '[default]';
|
||||
const currentFilePath = '/my/project/currentFile.js';
|
||||
const rootPath = '/my/project';
|
||||
|
||||
const rootFile = await trackDownIdentifier(source, identifierName, currentFilePath, rootPath);
|
||||
expect(rootFile).to.eql({
|
||||
file: './MyClass.js',
|
||||
specifier: '[default]',
|
||||
});
|
||||
});
|
||||
|
||||
it(`identifies the current project as internal source`, async () => {
|
||||
mockProject(
|
||||
{
|
||||
'./MyClass.js': `export default class {}`,
|
||||
'./currentFile.js': `
|
||||
import MyClass from 'my-project/MyClass.js';
|
||||
`,
|
||||
},
|
||||
{
|
||||
projectName: 'my-project',
|
||||
projectPath: '/my/project',
|
||||
},
|
||||
);
|
||||
|
||||
// Let's say we want to track down 'MyClass' in the code above
|
||||
const source = '#internal/source';
|
||||
const identifierName = '[default]';
|
||||
const currentFilePath = '/my/project/currentFile.js';
|
||||
const rootPath = '/my/project';
|
||||
const projectName = 'my-project';
|
||||
|
||||
const rootFile = await trackDownIdentifier(
|
||||
source,
|
||||
identifierName,
|
||||
currentFilePath,
|
||||
rootPath,
|
||||
projectName,
|
||||
);
|
||||
expect(rootFile).to.eql({
|
||||
file: './MyClass.js',
|
||||
specifier: '[default]',
|
||||
});
|
||||
});
|
||||
|
||||
it(`tracks down locally declared, reexported identifiers (without a source defined)`, async () => {
|
||||
mockProject(
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue