feat(providence): full swc support, including performant traverse tool
This commit is contained in:
parent
e063229891
commit
ea803894ae
52 changed files with 3584 additions and 459 deletions
|
|
@ -23,11 +23,10 @@
|
|||
"providence": "./src/cli/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dashboard/src",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"dashboard": "node ./dashboard/server.js --run-server --serve-from-package-root",
|
||||
"dashboard": "node ./src/dashboard/server.js --run-server --serve-from-package-root",
|
||||
"postinstall": "npx patch-package",
|
||||
"match-lion-imports": "npm run providence -- analyze match-imports --search-target-collection @lion-targets --reference-collection @lion-references --measure-perf --skip-check-match-compatibility",
|
||||
"providence": "node --max-old-space-size=8192 ./src/cli/index.js",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { QueryService } from '../program/core/QueryService.js';
|
|||
import { InputDataService } from '../program/core/InputDataService.js';
|
||||
import { toPosixPath } from '../program/utils/to-posix-path.js';
|
||||
import { getCurrentDir } from '../program/utils/get-current-dir.js';
|
||||
import { dashboardServer } from '../../dashboard/server.js';
|
||||
import { dashboardServer } from '../dashboard/server.js';
|
||||
import { _providenceModule } from '../program/providence.js';
|
||||
import { _cliHelpersModule } from './cli-helpers.js';
|
||||
import { _extendDocsModule } from './launch-providence-with-extend-docs.js';
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import fs from 'fs';
|
||||
import pathLib from 'path';
|
||||
import { startDevServer } from '@web/dev-server';
|
||||
import { ReportService } from '../src/program/core/ReportService.js';
|
||||
import { providenceConfUtil } from '../src/program/utils/providence-conf-util.js';
|
||||
import { getCurrentDir } from '../src/program/utils/get-current-dir.js';
|
||||
import { ReportService } from '../program/core/ReportService.js';
|
||||
import { providenceConfUtil } from '../program/utils/providence-conf-util.js';
|
||||
import { getCurrentDir } from '../program/utils/get-current-dir.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../types/index.js').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('../types/index.js').GatherFilesConfig} GatherFilesConfig
|
||||
* @typedef {import('../types/index.js').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('../../types/index.js').GatherFilesConfig} GatherFilesConfig
|
||||
* @typedef {import('../../types/index.js').AnalyzerName} AnalyzerName
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
/* eslint-disable no-shadow, no-param-reassign */
|
||||
import path from 'path';
|
||||
import t from '@babel/types';
|
||||
// @ts-ignore
|
||||
import babelTraverse from '@babel/traverse';
|
||||
import { Analyzer } from '../core/Analyzer.js';
|
||||
import { trackDownIdentifierFromScope } from './helpers/track-down-identifier.js';
|
||||
import { trackDownIdentifierFromScope } from './helpers/track-down-identifier--legacy.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@babel/types').File} File
|
||||
|
|
@ -227,7 +228,7 @@ export default class FindClassesAnalyzer extends Analyzer {
|
|||
static analyzerName = 'find-classes';
|
||||
|
||||
/** @type {'babel'|'swc-to-babel'} */
|
||||
requiredAst = 'babel';
|
||||
static requiredAst = 'babel';
|
||||
|
||||
/**
|
||||
* Will find all public members (properties (incl. getter/setters)/functions) of a class and
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import path from 'path';
|
|||
import t from '@babel/types';
|
||||
import babelTraverse from '@babel/traverse';
|
||||
import { Analyzer } from '../core/Analyzer.js';
|
||||
import { trackDownIdentifierFromScope } from './helpers/track-down-identifier.js';
|
||||
import { trackDownIdentifierFromScope } from './helpers/track-down-identifier--legacy.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@babel/types').File} File
|
||||
|
|
@ -94,7 +94,7 @@ export default class FindCustomelementsAnalyzer extends Analyzer {
|
|||
static analyzerName = 'find-customelements';
|
||||
|
||||
/** @type {'babel'|'swc-to-babel'} */
|
||||
requiredAst = 'swc-to-babel';
|
||||
static requiredAst = 'swc-to-babel';
|
||||
|
||||
/**
|
||||
* Finds export specifiers and sources
|
||||
|
|
|
|||
|
|
@ -0,0 +1,272 @@
|
|||
/* eslint-disable no-shadow, no-param-reassign */
|
||||
import pathLib from 'path';
|
||||
import babelTraverse from '@babel/traverse';
|
||||
import { Analyzer } from '../core/Analyzer.js';
|
||||
import { trackDownIdentifier } from './helpers/track-down-identifier--legacy.js';
|
||||
import { normalizeSourcePaths } from './helpers/normalize-source-paths.js';
|
||||
import { getReferencedDeclaration } from '../utils/get-source-code-fragment-of-declaration.js';
|
||||
import { LogService } from '../core/LogService.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@babel/types').File} File
|
||||
* @typedef {import('@babel/types').Node} Node
|
||||
* @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../../../types/index.js').FindExportsAnalyzerResult} FindExportsAnalyzerResult
|
||||
* @typedef {import('../../../types/index.js').FindExportsAnalyzerEntry} FindExportsAnalyzerEntry
|
||||
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
* @typedef {import('./helpers/track-down-identifier.js').RootFile} RootFile
|
||||
* @typedef {object} RootFileMapEntry
|
||||
* @typedef {string} currentFileSpecifier this is the local name in the file we track from
|
||||
* @typedef {RootFile} rootFile contains file(filePath) and specifier
|
||||
* @typedef {RootFileMapEntry[]} RootFileMap
|
||||
* @typedef {{ exportSpecifiers:string[]; localMap: object; source:string, __tmp: { path:string } }} FindExportsSpecifierObj
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {FindExportsSpecifierObj[]} transformedFile
|
||||
*/
|
||||
async function trackdownRoot(transformedFile, relativePath, projectPath) {
|
||||
const fullCurrentFilePath = pathLib.resolve(projectPath, relativePath);
|
||||
for (const specObj of transformedFile) {
|
||||
/** @type {RootFileMap} */
|
||||
const rootFileMap = [];
|
||||
if (specObj.exportSpecifiers[0] === '[file]') {
|
||||
rootFileMap.push(undefined);
|
||||
} else {
|
||||
/**
|
||||
* './src/origin.js': `export class MyComp {}`
|
||||
* './index.js:' `export { MyComp as RenamedMyComp } from './src/origin'`
|
||||
*
|
||||
* Goes from specifier like 'RenamedMyComp' to object for rootFileMap like:
|
||||
* {
|
||||
* currentFileSpecifier: 'RenamedMyComp',
|
||||
* rootFile: {
|
||||
* file: './src/origin.js',
|
||||
* specifier: 'MyCompDefinition',
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
for (const specifier of specObj.exportSpecifiers) {
|
||||
let rootFile;
|
||||
let localMapMatch;
|
||||
if (specObj.localMap) {
|
||||
localMapMatch = specObj.localMap.find(m => m.exported === specifier);
|
||||
}
|
||||
|
||||
// TODO: find out if possible to use trackDownIdentifierFromScope
|
||||
if (specObj.source) {
|
||||
// TODO: see if still needed: && (localMapMatch || specifier === '[default]')
|
||||
const importedIdentifier = localMapMatch?.local || specifier;
|
||||
|
||||
rootFile = await trackDownIdentifier(
|
||||
specObj.source,
|
||||
importedIdentifier,
|
||||
fullCurrentFilePath,
|
||||
projectPath,
|
||||
);
|
||||
|
||||
/** @type {RootFileMapEntry} */
|
||||
const entry = {
|
||||
currentFileSpecifier: specifier,
|
||||
rootFile,
|
||||
};
|
||||
rootFileMap.push(entry);
|
||||
} else {
|
||||
/** @type {RootFileMapEntry} */
|
||||
const entry = {
|
||||
currentFileSpecifier: specifier,
|
||||
rootFile: { file: '[current]', specifier },
|
||||
};
|
||||
rootFileMap.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
specObj.rootFileMap = rootFileMap;
|
||||
}
|
||||
return transformedFile;
|
||||
}
|
||||
|
||||
function cleanup(transformedFile) {
|
||||
transformedFile.forEach(specObj => {
|
||||
if (specObj.__tmp) {
|
||||
delete specObj.__tmp;
|
||||
}
|
||||
});
|
||||
return transformedFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function getExportSpecifiers(node) {
|
||||
// handles default [export const g = 4];
|
||||
if (node.declaration) {
|
||||
if (node.declaration.declarations) {
|
||||
return [node.declaration.declarations[0].id.name];
|
||||
}
|
||||
if (node.declaration.id) {
|
||||
return [node.declaration.id.name];
|
||||
}
|
||||
}
|
||||
|
||||
// handles (re)named specifiers [export { x (as y)} from 'y'];
|
||||
return node.specifiers.map(s => {
|
||||
let specifier;
|
||||
if (s.exported) {
|
||||
// { x as y }
|
||||
specifier = s.exported.name === 'default' ? '[default]' : s.exported.name;
|
||||
} else {
|
||||
// { x }
|
||||
specifier = s.local.name;
|
||||
}
|
||||
return specifier;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object[]}
|
||||
*/
|
||||
function getLocalNameSpecifiers(node) {
|
||||
return node.specifiers
|
||||
.map(s => {
|
||||
if (s.exported && s.local && s.exported.name !== s.local.name) {
|
||||
return {
|
||||
// if reserved keyword 'default' is used, translate it into 'providence keyword'
|
||||
local: s.local.name === 'default' ? '[default]' : s.local.name,
|
||||
exported: s.exported.name,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter(s => s);
|
||||
}
|
||||
|
||||
const isImportingSpecifier = pathOrNode =>
|
||||
pathOrNode.type === 'ImportDefaultSpecifier' || pathOrNode.type === 'ImportSpecifier';
|
||||
|
||||
/**
|
||||
* Finds import specifiers and sources for a given ast result
|
||||
* @param {File} babelAst
|
||||
* @param {FindExportsConfig} config
|
||||
*/
|
||||
function findExportsPerAstFile(babelAst, { skipFileImports }) {
|
||||
LogService.debug(`Analyzer "find-exports": started findExportsPerAstFile method`);
|
||||
|
||||
// Visit AST...
|
||||
|
||||
/** @type {FindExportsSpecifierObj[]} */
|
||||
const transformedFile = [];
|
||||
// Unfortunately, we cannot have async functions in babel traverse.
|
||||
// Therefore, we store a temp reference to path that we use later for
|
||||
// async post processing (tracking down original export Identifier)
|
||||
let globalScopeBindings;
|
||||
|
||||
babelTraverse.default(babelAst, {
|
||||
Program(babelPath) {
|
||||
// enter(babelPath) {
|
||||
const body = babelPath.get('body');
|
||||
if (body.length) {
|
||||
globalScopeBindings = body[0].scope.bindings;
|
||||
}
|
||||
// },
|
||||
},
|
||||
ExportNamedDeclaration(astPath) {
|
||||
const exportSpecifiers = getExportSpecifiers(astPath.node);
|
||||
const localMap = getLocalNameSpecifiers(astPath.node);
|
||||
const source = astPath.node.source?.value;
|
||||
const entry = { exportSpecifiers, localMap, source, __tmp: { astPath } };
|
||||
if (astPath.node.assertions?.length) {
|
||||
entry.assertionType = astPath.node.assertions[0].value?.value;
|
||||
}
|
||||
transformedFile.push(entry);
|
||||
},
|
||||
ExportDefaultDeclaration(defaultExportPath) {
|
||||
const exportSpecifiers = ['[default]'];
|
||||
let source;
|
||||
if (defaultExportPath.node.declaration?.type !== 'Identifier') {
|
||||
source = defaultExportPath.node.declaration.name;
|
||||
} else {
|
||||
const importOrDeclPath = getReferencedDeclaration({
|
||||
referencedIdentifierName: defaultExportPath.node.declaration.name,
|
||||
globalScopeBindings,
|
||||
});
|
||||
if (isImportingSpecifier(importOrDeclPath)) {
|
||||
source = importOrDeclPath.parentPath.node.source.value;
|
||||
}
|
||||
}
|
||||
transformedFile.push({ exportSpecifiers, source, __tmp: { astPath: defaultExportPath } });
|
||||
},
|
||||
});
|
||||
|
||||
if (!skipFileImports) {
|
||||
// Always add an entry for just the file 'relativePath'
|
||||
// (since this also can be imported directly from a search target project)
|
||||
transformedFile.push({
|
||||
exportSpecifiers: ['[file]'],
|
||||
// source: relativePath,
|
||||
});
|
||||
}
|
||||
|
||||
return transformedFile;
|
||||
}
|
||||
|
||||
export default class FindExportsAnalyzer extends Analyzer {
|
||||
/** @type {AnalyzerName} */
|
||||
static analyzerName = 'find-exports';
|
||||
|
||||
/** @type {'babel'|'swc-to-babel'} */
|
||||
static requiredAst = 'swc-to-babel';
|
||||
|
||||
/**
|
||||
* Finds export specifiers and sources
|
||||
* @param {FindExportsConfig} customConfig
|
||||
*/
|
||||
async execute(customConfig = {}) {
|
||||
/**
|
||||
* @typedef FindExportsConfig
|
||||
* @property {boolean} [onlyInternalSources=false]
|
||||
* @property {boolean} [skipFileImports=false] Instead of both focusing on specifiers like
|
||||
* [import {specifier} 'lion-based-ui/foo.js'], and [import 'lion-based-ui/foo.js'] as a result,
|
||||
* not list file exports
|
||||
*/
|
||||
const cfg = {
|
||||
targetProjectPath: null,
|
||||
skipFileImports: false,
|
||||
...customConfig,
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare
|
||||
*/
|
||||
const cachedAnalyzerResult = this._prepare(cfg);
|
||||
if (cachedAnalyzerResult) {
|
||||
return cachedAnalyzerResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse
|
||||
*/
|
||||
const projectPath = cfg.targetProjectPath;
|
||||
|
||||
const traverseEntryFn = async (ast, { relativePath }) => {
|
||||
let transformedFile = findExportsPerAstFile(ast, cfg);
|
||||
|
||||
transformedFile = await normalizeSourcePaths(transformedFile, relativePath, projectPath);
|
||||
transformedFile = await trackdownRoot(transformedFile, relativePath, projectPath);
|
||||
transformedFile = cleanup(transformedFile);
|
||||
|
||||
return { result: transformedFile };
|
||||
};
|
||||
|
||||
const queryOutput = await this._traverse({
|
||||
traverseEntryFn,
|
||||
filePaths: cfg.targetFilePaths,
|
||||
projectPath: cfg.targetProjectPath,
|
||||
});
|
||||
|
||||
/**
|
||||
* Finalize
|
||||
*/
|
||||
return this._finalize(queryOutput, cfg);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable no-shadow, no-param-reassign */
|
||||
import pathLib from 'path';
|
||||
import babelTraverse from '@babel/traverse';
|
||||
import path from 'path';
|
||||
import { swcTraverse } from '../utils/swc-traverse.js';
|
||||
import { Analyzer } from '../core/Analyzer.js';
|
||||
import { trackDownIdentifier } from './helpers/track-down-identifier.js';
|
||||
import { normalizeSourcePaths } from './helpers/normalize-source-paths.js';
|
||||
|
|
@ -8,12 +8,18 @@ import { getReferencedDeclaration } from '../utils/get-source-code-fragment-of-d
|
|||
import { LogService } from '../core/LogService.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@babel/types').File} File
|
||||
* @typedef {import('@babel/types').Node} Node
|
||||
* @typedef {import("@swc/core").Module} SwcAstModule
|
||||
* @typedef {import("@swc/core").Node} SwcNode
|
||||
* @typedef {import("@swc/core").VariableDeclaration} SwcVariableDeclaration
|
||||
* @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
|
||||
* @typedef {import('../../../types/index.js').FindExportsAnalyzerResult} FindExportsAnalyzerResult
|
||||
* @typedef {import('../../../types/index.js').FindExportsAnalyzerEntry} FindExportsAnalyzerEntry
|
||||
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
* @typedef {import('../../../types/index.js').SwcScope} SwcScope
|
||||
* @typedef {import('../../../types/index.js').SwcBinding} SwcBinding
|
||||
* @typedef {import('../../../types/index.js').SwcPath} SwcPath
|
||||
* @typedef {import('../../../types/index.js').SwcVisitor} SwcVisitor
|
||||
* @typedef {import('./helpers/track-down-identifier.js').RootFile} RootFile
|
||||
* @typedef {object} RootFileMapEntry
|
||||
* @typedef {string} currentFileSpecifier this is the local name in the file we track from
|
||||
|
|
@ -26,7 +32,7 @@ import { LogService } from '../core/LogService.js';
|
|||
* @param {FindExportsSpecifierObj[]} transformedFile
|
||||
*/
|
||||
async function trackdownRoot(transformedFile, relativePath, projectPath) {
|
||||
const fullCurrentFilePath = pathLib.resolve(projectPath, relativePath);
|
||||
const fullCurrentFilePath = path.resolve(projectPath, relativePath);
|
||||
for (const specObj of transformedFile) {
|
||||
/** @type {RootFileMap} */
|
||||
const rootFileMap = [];
|
||||
|
|
@ -96,49 +102,47 @@ function cleanup(transformedFile) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {*} node
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function getExportSpecifiers(node) {
|
||||
// handles default [export const g = 4];
|
||||
if (node.declaration) {
|
||||
if (node.declaration.declarations) {
|
||||
return [node.declaration.declarations[0].id.name];
|
||||
return [node.declaration.declarations[0].id.value];
|
||||
}
|
||||
if (node.declaration.id) {
|
||||
return [node.declaration.id.name];
|
||||
if (node.declaration.identifier) {
|
||||
return [node.declaration.identifier.value];
|
||||
}
|
||||
}
|
||||
|
||||
// handles (re)named specifiers [export { x (as y)} from 'y'];
|
||||
return node.specifiers.map(s => {
|
||||
let specifier;
|
||||
return (node.specifiers || []).map(s => {
|
||||
if (s.exported) {
|
||||
// { x as y }
|
||||
specifier = s.exported.name === 'default' ? '[default]' : s.exported.name;
|
||||
} else {
|
||||
// { x }
|
||||
specifier = s.local.name;
|
||||
return s.exported.value === 'default' ? '[default]' : s.exported.value;
|
||||
}
|
||||
return specifier;
|
||||
// { x }
|
||||
return s.orig.value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {object[]}
|
||||
* @returns {{local:string;exported:string;}|undefined[]}
|
||||
*/
|
||||
function getLocalNameSpecifiers(node) {
|
||||
return node.specifiers
|
||||
return (node.declaration?.declarations || node.specifiers || [])
|
||||
.map(s => {
|
||||
if (s.exported && s.local && s.exported.name !== s.local.name) {
|
||||
if (s.exported && s.orig && s.exported.value !== s.orig.value) {
|
||||
return {
|
||||
// if reserved keyword 'default' is used, translate it into 'providence keyword'
|
||||
local: s.local.name === 'default' ? '[default]' : s.local.name,
|
||||
exported: s.exported.name,
|
||||
local: s.orig.value === 'default' ? '[default]' : s.orig.value,
|
||||
exported: s.exported.value,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter(s => s);
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
const isImportingSpecifier = pathOrNode =>
|
||||
|
|
@ -146,10 +150,10 @@ const isImportingSpecifier = pathOrNode =>
|
|||
|
||||
/**
|
||||
* Finds import specifiers and sources for a given ast result
|
||||
* @param {File} babelAst
|
||||
* @param {SwcAstModule} swcAst
|
||||
* @param {FindExportsConfig} config
|
||||
*/
|
||||
function findExportsPerAstFile(babelAst, { skipFileImports }) {
|
||||
function findExportsPerAstFile(swcAst, { skipFileImports }) {
|
||||
LogService.debug(`Analyzer "find-exports": started findExportsPerAstFile method`);
|
||||
|
||||
// Visit AST...
|
||||
|
|
@ -159,44 +163,52 @@ function findExportsPerAstFile(babelAst, { skipFileImports }) {
|
|||
// Unfortunately, we cannot have async functions in babel traverse.
|
||||
// Therefore, we store a temp reference to path that we use later for
|
||||
// async post processing (tracking down original export Identifier)
|
||||
/** @type {{[key:string]:SwcBinding}} */
|
||||
let globalScopeBindings;
|
||||
|
||||
babelTraverse.default(babelAst, {
|
||||
Program: {
|
||||
enter(babelPath) {
|
||||
const body = babelPath.get('body');
|
||||
if (body.length) {
|
||||
globalScopeBindings = body[0].scope.bindings;
|
||||
}
|
||||
},
|
||||
},
|
||||
ExportNamedDeclaration(astPath) {
|
||||
const exportHandler = (/** @type {SwcPath} */ astPath) => {
|
||||
const exportSpecifiers = getExportSpecifiers(astPath.node);
|
||||
const localMap = getLocalNameSpecifiers(astPath.node);
|
||||
const source = astPath.node.source?.value;
|
||||
const entry = { exportSpecifiers, localMap, source, __tmp: { astPath } };
|
||||
if (astPath.node.assertions?.length) {
|
||||
entry.assertionType = astPath.node.assertions[0].value?.value;
|
||||
if (astPath.node.asserts) {
|
||||
entry.assertionType = astPath.node.asserts.properties[0].value?.value;
|
||||
}
|
||||
transformedFile.push(entry);
|
||||
},
|
||||
ExportDefaultDeclaration(defaultExportPath) {
|
||||
};
|
||||
|
||||
const exportDefaultHandler = (/** @type {SwcPath} */ astPath) => {
|
||||
const exportSpecifiers = ['[default]'];
|
||||
let source;
|
||||
if (defaultExportPath.node.declaration?.type !== 'Identifier') {
|
||||
source = defaultExportPath.node.declaration.name;
|
||||
} else {
|
||||
// Is it an inline declaration like "export default class X {};" ?
|
||||
if (
|
||||
astPath.node.decl?.type === 'Identifier' ||
|
||||
astPath.node.expression?.type === 'Identifier'
|
||||
) {
|
||||
// It is a reference to an identifier like "export { x } from 'y';"
|
||||
const importOrDeclPath = getReferencedDeclaration({
|
||||
referencedIdentifierName: defaultExportPath.node.declaration.name,
|
||||
referencedIdentifierName: astPath.node.decl?.value || astPath.node.expression.value,
|
||||
globalScopeBindings,
|
||||
});
|
||||
if (isImportingSpecifier(importOrDeclPath)) {
|
||||
source = importOrDeclPath.parentPath.node.source.value;
|
||||
}
|
||||
}
|
||||
transformedFile.push({ exportSpecifiers, source, __tmp: { astPath: defaultExportPath } });
|
||||
transformedFile.push({ exportSpecifiers, source, __tmp: { astPath } });
|
||||
};
|
||||
|
||||
/** @type {SwcVisitor} */
|
||||
const visitor = {
|
||||
Module({ scope }) {
|
||||
globalScopeBindings = scope.bindings;
|
||||
},
|
||||
});
|
||||
ExportDeclaration: exportHandler,
|
||||
ExportNamedDeclaration: exportHandler,
|
||||
ExportDefaultDeclaration: exportDefaultHandler,
|
||||
ExportDefaultExpression: exportDefaultHandler,
|
||||
};
|
||||
|
||||
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true });
|
||||
|
||||
if (!skipFileImports) {
|
||||
// Always add an entry for just the file 'relativePath'
|
||||
|
|
@ -211,17 +223,10 @@ function findExportsPerAstFile(babelAst, { skipFileImports }) {
|
|||
}
|
||||
|
||||
export default class FindExportsAnalyzer extends Analyzer {
|
||||
/** @type {AnalyzerName} */
|
||||
static analyzerName = 'find-exports';
|
||||
static analyzerName = /** @type {AnalyzerName} */ ('find-exports');
|
||||
|
||||
/** @type {'babel'|'swc-to-babel'} */
|
||||
requiredAst = 'swc-to-babel';
|
||||
static requiredAst = /** @type {AnalyzerAst} */ ('swc');
|
||||
|
||||
/**
|
||||
* Finds export specifiers and sources
|
||||
* @param {FindExportsConfig} customConfig
|
||||
*/
|
||||
async execute(customConfig = {}) {
|
||||
/**
|
||||
* @typedef FindExportsConfig
|
||||
* @property {boolean} [onlyInternalSources=false]
|
||||
|
|
@ -229,44 +234,23 @@ export default class FindExportsAnalyzer extends Analyzer {
|
|||
* [import {specifier} 'lion-based-ui/foo.js'], and [import 'lion-based-ui/foo.js'] as a result,
|
||||
* not list file exports
|
||||
*/
|
||||
const cfg = {
|
||||
get config() {
|
||||
return {
|
||||
targetProjectPath: null,
|
||||
skipFileImports: false,
|
||||
...customConfig,
|
||||
...this._customConfig,
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare
|
||||
*/
|
||||
const cachedAnalyzerResult = this._prepare(cfg);
|
||||
if (cachedAnalyzerResult) {
|
||||
return cachedAnalyzerResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse
|
||||
*/
|
||||
const projectPath = cfg.targetProjectPath;
|
||||
static async analyzeFile(ast, { relativePath, analyzerCfg }) {
|
||||
const projectPath = analyzerCfg.targetProjectPath;
|
||||
|
||||
const traverseEntryFn = async (ast, { relativePath }) => {
|
||||
let transformedFile = findExportsPerAstFile(ast, cfg);
|
||||
let transformedFile = findExportsPerAstFile(ast, analyzerCfg);
|
||||
|
||||
transformedFile = await normalizeSourcePaths(transformedFile, relativePath, projectPath);
|
||||
transformedFile = await trackdownRoot(transformedFile, relativePath, projectPath);
|
||||
transformedFile = cleanup(transformedFile);
|
||||
|
||||
return { result: transformedFile };
|
||||
};
|
||||
|
||||
const queryOutput = await this._traverse({
|
||||
traverseEntryFn,
|
||||
filePaths: cfg.targetFilePaths,
|
||||
projectPath: cfg.targetProjectPath,
|
||||
});
|
||||
|
||||
/**
|
||||
* Finalize
|
||||
*/
|
||||
return this._finalize(queryOutput, cfg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,189 @@
|
|||
/* eslint-disable no-shadow, no-param-reassign */
|
||||
import babelTraverse from '@babel/traverse';
|
||||
import { isRelativeSourcePath } from '../utils/relative-source-path.js';
|
||||
import { normalizeSourcePaths } from './helpers/normalize-source-paths.js';
|
||||
import { Analyzer } from '../core/Analyzer.js';
|
||||
import { LogService } from '../core/LogService.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@babel/types').File} File
|
||||
* @typedef {import('@babel/types').Node} Node
|
||||
* @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../../../types/index.js').AnalyzerConfig} AnalyzerConfig
|
||||
* @typedef {import('../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult
|
||||
* @typedef {import('../../../types/index.js').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
|
||||
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options that allow to filter 'on a file basis'.
|
||||
* We can also filter on the total result
|
||||
*/
|
||||
const /** @type {AnalyzerConfig} */ options = {
|
||||
/**
|
||||
* Only leaves entries with external sources:
|
||||
* - keeps: '@open-wc/testing'
|
||||
* - drops: '../testing'
|
||||
* @param {FindImportsAnalyzerQueryOutput} result
|
||||
* @param {string} targetSpecifier for instance 'LitElement'
|
||||
*/
|
||||
onlyExternalSources(result) {
|
||||
return result.filter(entry => !isRelativeSourcePath(entry.source));
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
*/
|
||||
function getImportOrReexportsSpecifiers(node) {
|
||||
return node.specifiers.map(s => {
|
||||
if (
|
||||
s.type === 'ImportDefaultSpecifier' ||
|
||||
s.type === 'ExportDefaultSpecifier' ||
|
||||
(s.type === 'ExportSpecifier' && s.exported?.name === 'default')
|
||||
) {
|
||||
return '[default]';
|
||||
}
|
||||
if (s.type === 'ImportNamespaceSpecifier' || s.type === 'ExportNamespaceSpecifier') {
|
||||
return '[*]';
|
||||
}
|
||||
if ((s.imported && s.type === 'ImportNamespaceSpecifier') || s.type === 'ImportSpecifier') {
|
||||
return s.imported.name;
|
||||
}
|
||||
if (s.exported && s.type === 'ExportNamespaceSpecifier') {
|
||||
return s.exported.name;
|
||||
}
|
||||
return s.local.name;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds import specifiers and sources
|
||||
* @param {File} babelAst
|
||||
*/
|
||||
function findImportsPerAstFile(babelAst, context) {
|
||||
LogService.debug(`Analyzer "find-imports": started findImportsPerAstFile method`);
|
||||
|
||||
// https://github.com/babel/babel/blob/672a58660f0b15691c44582f1f3fdcdac0fa0d2f/packages/babel-core/src/transformation/index.ts#L110
|
||||
// Visit AST...
|
||||
/** @type {Partial<FindImportsAnalyzerEntry>[]} */
|
||||
const transformedFile = [];
|
||||
babelTraverse.default(babelAst, {
|
||||
ImportDeclaration(path) {
|
||||
const importSpecifiers = getImportOrReexportsSpecifiers(path.node);
|
||||
if (!importSpecifiers.length) {
|
||||
importSpecifiers.push('[file]'); // apparently, there was just a file import
|
||||
}
|
||||
const source = path.node.source.value;
|
||||
const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source });
|
||||
if (path.node.assertions?.length) {
|
||||
entry.assertionType = path.node.assertions[0].value?.value;
|
||||
}
|
||||
transformedFile.push(entry);
|
||||
},
|
||||
// Dynamic imports
|
||||
CallExpression(path) {
|
||||
if (path.node.callee?.type !== 'Import') {
|
||||
return;
|
||||
}
|
||||
// TODO: check for specifiers catched via obj destructuring?
|
||||
// TODO: also check for ['file']
|
||||
const importSpecifiers = ['[default]'];
|
||||
let source = path.node.arguments[0].value;
|
||||
if (!source) {
|
||||
// TODO: with advanced retrieval, we could possibly get the value
|
||||
source = '[variable]';
|
||||
}
|
||||
transformedFile.push({ importSpecifiers, source });
|
||||
},
|
||||
ExportNamedDeclaration(path) {
|
||||
if (!path.node.source) {
|
||||
return; // we are dealing with a regular export, not a reexport
|
||||
}
|
||||
const importSpecifiers = getImportOrReexportsSpecifiers(path.node);
|
||||
const source = path.node.source.value;
|
||||
const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source });
|
||||
if (path.node.assertions?.length) {
|
||||
entry.assertionType = path.node.assertions[0].value?.value;
|
||||
}
|
||||
transformedFile.push(entry);
|
||||
},
|
||||
// ExportAllDeclaration(path) {
|
||||
// if (!path.node.source) {
|
||||
// return; // we are dealing with a regular export, not a reexport
|
||||
// }
|
||||
// const importSpecifiers = ['[*]'];
|
||||
// const source = path.node.source.value;
|
||||
// transformedFile.push({ importSpecifiers, source });
|
||||
// },
|
||||
});
|
||||
|
||||
return transformedFile;
|
||||
}
|
||||
|
||||
export default class FindImportsAnalyzer extends Analyzer {
|
||||
/** @type {AnalyzerName} */
|
||||
static analyzerName = 'find-imports';
|
||||
|
||||
/** @type {'babel'|'swc-to-babel'} */
|
||||
requiredAst = 'swc-to-babel';
|
||||
|
||||
/**
|
||||
* Finds import specifiers and sources
|
||||
* @param {FindImportsConfig} customConfig
|
||||
*/
|
||||
async execute(customConfig = {}) {
|
||||
/**
|
||||
* @typedef FindImportsConfig
|
||||
* @property {boolean} [keepInternalSources=false] by default, relative paths like '../x.js' are
|
||||
* filtered out. This option keeps them.
|
||||
* means that 'external-dep/file' will be resolved to 'external-dep/file.js' will both be stored
|
||||
* as the latter
|
||||
*/
|
||||
const cfg = {
|
||||
targetProjectPath: null,
|
||||
// post process file
|
||||
keepInternalSources: false,
|
||||
...customConfig,
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare
|
||||
*/
|
||||
const cachedAnalyzerResult = this._prepare(cfg);
|
||||
if (cachedAnalyzerResult) {
|
||||
return cachedAnalyzerResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse
|
||||
*/
|
||||
const queryOutput = await this._traverse(async (ast, context) => {
|
||||
let transformedFile = findImportsPerAstFile(ast, context);
|
||||
// Post processing based on configuration...
|
||||
transformedFile = await normalizeSourcePaths(
|
||||
transformedFile,
|
||||
context.relativePath,
|
||||
cfg.targetProjectPath,
|
||||
);
|
||||
|
||||
if (!cfg.keepInternalSources) {
|
||||
transformedFile = options.onlyExternalSources(transformedFile);
|
||||
}
|
||||
|
||||
return { result: transformedFile };
|
||||
});
|
||||
|
||||
// if (cfg.sortBySpecifier) {
|
||||
// queryOutput = sortBySpecifier.execute(queryOutput, {
|
||||
// ...cfg,
|
||||
// specifiersKey: 'importSpecifiers',
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* Finalize
|
||||
*/
|
||||
return this._finalize(queryOutput, cfg);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
/* eslint-disable no-shadow, no-param-reassign */
|
||||
import babelTraverse from '@babel/traverse';
|
||||
import { isRelativeSourcePath } from '../utils/relative-source-path.js';
|
||||
import { swcTraverse } from '../utils/swc-traverse.js';
|
||||
import { normalizeSourcePaths } from './helpers/normalize-source-paths.js';
|
||||
import { Analyzer } from '../core/Analyzer.js';
|
||||
import { LogService } from '../core/LogService.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@babel/types').File} File
|
||||
* @typedef {import('@babel/types').Node} Node
|
||||
* @typedef {import("@swc/core").Module} SwcAstModule
|
||||
* @typedef {import("@swc/core").Node} SwcNode
|
||||
* @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
|
||||
* @typedef {import('../../../types/index.js').AnalyzerConfig} AnalyzerConfig
|
||||
* @typedef {import('../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult
|
||||
* @typedef {import('../../../types/index.js').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
|
||||
|
|
@ -16,117 +17,86 @@ import { LogService } from '../core/LogService.js';
|
|||
*/
|
||||
|
||||
/**
|
||||
* Options that allow to filter 'on a file basis'.
|
||||
* We can also filter on the total result
|
||||
*/
|
||||
const /** @type {AnalyzerConfig} */ options = {
|
||||
/**
|
||||
* Only leaves entries with external sources:
|
||||
* - keeps: '@open-wc/testing'
|
||||
* - drops: '../testing'
|
||||
* @param {FindImportsAnalyzerQueryOutput} result
|
||||
* @param {string} targetSpecifier for instance 'LitElement'
|
||||
*/
|
||||
onlyExternalSources(result) {
|
||||
return result.filter(entry => !isRelativeSourcePath(entry.source));
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {SwcNode} node
|
||||
*/
|
||||
function getImportOrReexportsSpecifiers(node) {
|
||||
return node.specifiers.map(s => {
|
||||
if (
|
||||
s.type === 'ImportDefaultSpecifier' ||
|
||||
s.type === 'ExportDefaultSpecifier' ||
|
||||
(s.type === 'ExportSpecifier' && s.exported?.name === 'default')
|
||||
(s.type === 'ExportSpecifier' && s.exported?.value === 'default')
|
||||
) {
|
||||
return '[default]';
|
||||
}
|
||||
if (s.type === 'ImportNamespaceSpecifier' || s.type === 'ExportNamespaceSpecifier') {
|
||||
return '[*]';
|
||||
}
|
||||
if ((s.imported && s.type === 'ImportNamespaceSpecifier') || s.type === 'ImportSpecifier') {
|
||||
return s.imported.name;
|
||||
}
|
||||
if (s.exported && s.type === 'ExportNamespaceSpecifier') {
|
||||
return s.exported.name;
|
||||
}
|
||||
return s.local.name;
|
||||
const importedValue = s.imported?.value || s.orig?.value || s.exported?.value || s.local?.value;
|
||||
return importedValue;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds import specifiers and sources
|
||||
* @param {File} babelAst
|
||||
* @param {SwcAstModule} swcAst
|
||||
*/
|
||||
function findImportsPerAstFile(babelAst, context) {
|
||||
function findImportsPerAstFile(swcAst, context) {
|
||||
LogService.debug(`Analyzer "find-imports": started findImportsPerAstFile method`);
|
||||
|
||||
// https://github.com/babel/babel/blob/672a58660f0b15691c44582f1f3fdcdac0fa0d2f/packages/babel-core/src/transformation/index.ts#L110
|
||||
// Visit AST...
|
||||
/** @type {Partial<FindImportsAnalyzerEntry>[]} */
|
||||
const transformedFile = [];
|
||||
babelTraverse.default(babelAst, {
|
||||
ImportDeclaration(path) {
|
||||
const importSpecifiers = getImportOrReexportsSpecifiers(path.node);
|
||||
|
||||
swcTraverse(swcAst, {
|
||||
ImportDeclaration({ node }) {
|
||||
const importSpecifiers = getImportOrReexportsSpecifiers(node);
|
||||
if (!importSpecifiers.length) {
|
||||
importSpecifiers.push('[file]'); // apparently, there was just a file import
|
||||
}
|
||||
const source = path.node.source.value;
|
||||
const source = node.source.value;
|
||||
const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source });
|
||||
if (path.node.assertions?.length) {
|
||||
entry.assertionType = path.node.assertions[0].value?.value;
|
||||
if (node.asserts) {
|
||||
entry.assertionType = node.asserts.properties[0].value?.value;
|
||||
}
|
||||
transformedFile.push(entry);
|
||||
},
|
||||
ExportNamedDeclaration({ node }) {
|
||||
if (!node.source) {
|
||||
return; // we are dealing with a regular export, not a reexport
|
||||
}
|
||||
const importSpecifiers = getImportOrReexportsSpecifiers(node);
|
||||
const source = node.source.value;
|
||||
const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source });
|
||||
if (node.asserts) {
|
||||
entry.assertionType = node.asserts.properties[0].value?.value;
|
||||
}
|
||||
transformedFile.push(entry);
|
||||
},
|
||||
// Dynamic imports
|
||||
CallExpression(path) {
|
||||
if (path.node.callee?.type !== 'Import') {
|
||||
CallExpression({ node }) {
|
||||
if (node.callee?.type !== 'Import') {
|
||||
return;
|
||||
}
|
||||
// TODO: check for specifiers catched via obj destructuring?
|
||||
// TODO: also check for ['file']
|
||||
const importSpecifiers = ['[default]'];
|
||||
let source = path.node.arguments[0].value;
|
||||
if (!source) {
|
||||
// TODO: with advanced retrieval, we could possibly get the value
|
||||
source = '[variable]';
|
||||
}
|
||||
const dynamicImportExpression = node.arguments[0].expression;
|
||||
const source =
|
||||
dynamicImportExpression.type === 'StringLiteral'
|
||||
? dynamicImportExpression.value
|
||||
: '[variable]';
|
||||
transformedFile.push({ importSpecifiers, source });
|
||||
},
|
||||
ExportNamedDeclaration(path) {
|
||||
if (!path.node.source) {
|
||||
return; // we are dealing with a regular export, not a reexport
|
||||
}
|
||||
const importSpecifiers = getImportOrReexportsSpecifiers(path.node);
|
||||
const source = path.node.source.value;
|
||||
const entry = /** @type {Partial<FindImportsAnalyzerEntry>} */ ({ importSpecifiers, source });
|
||||
if (path.node.assertions?.length) {
|
||||
entry.assertionType = path.node.assertions[0].value?.value;
|
||||
}
|
||||
transformedFile.push(entry);
|
||||
},
|
||||
// ExportAllDeclaration(path) {
|
||||
// if (!path.node.source) {
|
||||
// return; // we are dealing with a regular export, not a reexport
|
||||
// }
|
||||
// const importSpecifiers = ['[*]'];
|
||||
// const source = path.node.source.value;
|
||||
// transformedFile.push({ importSpecifiers, source });
|
||||
// },
|
||||
});
|
||||
|
||||
return transformedFile;
|
||||
}
|
||||
|
||||
export default class FindImportsAnalyzer extends Analyzer {
|
||||
/** @type {AnalyzerName} */
|
||||
static analyzerName = 'find-imports';
|
||||
export default class FindImportsSwcAnalyzer extends Analyzer {
|
||||
static analyzerName = /** @type {AnalyzerName} */ ('find-imports');
|
||||
|
||||
/** @type {'babel'|'swc-to-babel'} */
|
||||
requiredAst = 'swc-to-babel';
|
||||
static requiredAst = /** @type {AnalyzerAst} */ ('swc');
|
||||
|
||||
/**
|
||||
* Finds import specifiers and sources
|
||||
|
|
@ -158,8 +128,8 @@ export default class FindImportsAnalyzer extends Analyzer {
|
|||
/**
|
||||
* Traverse
|
||||
*/
|
||||
const queryOutput = await this._traverse(async (ast, context) => {
|
||||
let transformedFile = findImportsPerAstFile(ast, context);
|
||||
const queryOutput = await this._traverse(async (swcAst, context) => {
|
||||
let transformedFile = findImportsPerAstFile(swcAst, context);
|
||||
// Post processing based on configuration...
|
||||
transformedFile = await normalizeSourcePaths(
|
||||
transformedFile,
|
||||
|
|
@ -168,19 +138,12 @@ export default class FindImportsAnalyzer extends Analyzer {
|
|||
);
|
||||
|
||||
if (!cfg.keepInternalSources) {
|
||||
transformedFile = options.onlyExternalSources(transformedFile);
|
||||
transformedFile = transformedFile.filter(entry => !isRelativeSourcePath(entry.source));
|
||||
}
|
||||
|
||||
return { result: transformedFile };
|
||||
});
|
||||
|
||||
// if (cfg.sortBySpecifier) {
|
||||
// queryOutput = sortBySpecifier.execute(queryOutput, {
|
||||
// ...cfg,
|
||||
// specifiersKey: 'importSpecifiers',
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* Finalize
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,330 @@
|
|||
import fs from 'fs';
|
||||
import pathLib from 'path';
|
||||
import babelTraverse from '@babel/traverse';
|
||||
import { isRelativeSourcePath, toRelativeSourcePath } from '../../utils/relative-source-path.js';
|
||||
import { InputDataService } from '../../core/InputDataService.js';
|
||||
import { resolveImportPath } from '../../utils/resolve-import-path.js';
|
||||
import { AstService } from '../../core/AstService.js';
|
||||
import { LogService } from '../../core/LogService.js';
|
||||
import { memoize } from '../../utils/memoize.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../../../../types/index.js').RootFile} RootFile
|
||||
* @typedef {import('../../../../types/index.js').SpecifierSource} SpecifierSource
|
||||
* @typedef {import('../../../../types/index.js').IdentifierName} IdentifierName
|
||||
* @typedef {import('../../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('@babel/traverse').NodePath} NodePath
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @param {string} projectName
|
||||
*/
|
||||
function isSelfReferencingProject(source, projectName) {
|
||||
return source.startsWith(`${projectName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @param {string} projectName
|
||||
*/
|
||||
function isExternalProject(source, projectName) {
|
||||
return (
|
||||
!source.startsWith('#') &&
|
||||
!isRelativeSourcePath(source) &&
|
||||
!isSelfReferencingProject(source, projectName)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Other than with import, no binding is created for MyClass by Babel(?)
|
||||
* This means 'path.scope.getBinding('MyClass')' returns undefined
|
||||
* and we have to find a different way to retrieve this value.
|
||||
* @param {NodePath} astPath Babel ast traversal path
|
||||
* @param {IdentifierName} identifierName the name that should be tracked (and that exists inside scope of astPath)
|
||||
*/
|
||||
function getBindingAndSourceReexports(astPath, identifierName) {
|
||||
// Get to root node of file and look for exports like `export { identifierName } from 'src';`
|
||||
let source;
|
||||
let bindingType;
|
||||
let bindingPath;
|
||||
|
||||
let curPath = astPath;
|
||||
while (curPath.parentPath) {
|
||||
curPath = curPath.parentPath;
|
||||
}
|
||||
const rootPath = curPath;
|
||||
rootPath.traverse({
|
||||
ExportSpecifier(astPath) {
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
const found =
|
||||
astPath.node.exported.name === identifierName || astPath.node.local.name === identifierName;
|
||||
if (found) {
|
||||
bindingPath = astPath;
|
||||
bindingType = 'ExportSpecifier';
|
||||
source = astPath.parentPath.node.source
|
||||
? astPath.parentPath.node.source.value
|
||||
: '[current]';
|
||||
astPath.stop();
|
||||
}
|
||||
},
|
||||
});
|
||||
return [source, bindingType, bindingPath];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves source (like '@lion/core') and importedIdentifierName (like 'lit') from ast for
|
||||
* current file.
|
||||
* We might be an import that was locally renamed.
|
||||
* Since we are traversing, we are interested in the imported name. Or in case of a re-export,
|
||||
* the local name.
|
||||
* @param {NodePath} astPath Babel ast traversal path
|
||||
* @param {string} identifierName the name that should be tracked (and that exists inside scope of astPath)
|
||||
* @returns {{ source:string, importedIdentifierName:string }}
|
||||
*/
|
||||
export function getImportSourceFromAst(astPath, identifierName) {
|
||||
let source;
|
||||
let importedIdentifierName;
|
||||
|
||||
const binding = astPath.scope.getBinding(identifierName);
|
||||
let bindingType = binding?.path.type;
|
||||
let bindingPath = binding?.path;
|
||||
const matchingTypes = ['ImportSpecifier', 'ImportDefaultSpecifier', 'ExportSpecifier'];
|
||||
|
||||
if (bindingType && matchingTypes.includes(bindingType)) {
|
||||
source = binding?.path?.parentPath?.node?.source?.value;
|
||||
} else {
|
||||
// no binding
|
||||
[source, bindingType, bindingPath] = getBindingAndSourceReexports(astPath, identifierName);
|
||||
}
|
||||
|
||||
const shouldLookForDefaultExport = bindingType === 'ImportDefaultSpecifier';
|
||||
if (shouldLookForDefaultExport) {
|
||||
importedIdentifierName = '[default]';
|
||||
} else if (source) {
|
||||
const { node } = bindingPath;
|
||||
importedIdentifierName = (node.imported && node.imported.name) || node.local.name;
|
||||
}
|
||||
|
||||
return { source, importedIdentifierName };
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {(source:SpecifierSource,identifierName:IdentifierName,currentFilePath:PathFromSystemRoot,rootPath:PathFromSystemRoot,projectName?: string,depth?:number) => Promise<RootFile>} TrackDownIdentifierFn
|
||||
*/
|
||||
|
||||
/**
|
||||
* Follows the full path of an Identifier until its declaration ('root file') is found.
|
||||
* @example
|
||||
*```js
|
||||
* // 1. Starting point
|
||||
* // target-proj/my-comp-import.js
|
||||
* import { MyComp as TargetComp } from 'ref-proj';
|
||||
*
|
||||
* // 2. Intermediate stop: a re-export
|
||||
* // ref-proj/exportsIndex.js (package.json has main: './exportsIndex.js')
|
||||
* export { RefComp as MyComp } from './src/RefComp.js';
|
||||
*
|
||||
* // 3. End point: our declaration
|
||||
* // ref-proj/src/RefComp.js
|
||||
* export class RefComp extends LitElement {...}
|
||||
*```
|
||||
*
|
||||
* -param {SpecifierSource} source an importSpecifier source, like 'ref-proj' or '../file'
|
||||
* -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'
|
||||
*/
|
||||
/** @type {TrackDownIdentifierFn} */
|
||||
// eslint-disable-next-line import/no-mutable-exports
|
||||
export let trackDownIdentifier;
|
||||
|
||||
/** @type {TrackDownIdentifierFn} */
|
||||
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 (!projectName) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
projectName = InputDataService.getPackageJson(rootPath)?.name;
|
||||
}
|
||||
|
||||
if (projectName && 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.
|
||||
// In case this helper is used inside an analyzer like 'match-subclasses', the external
|
||||
// (search-target) project can be accessed and paths can be resolved to local ones,
|
||||
// just like in 'match-imports' analyzer.
|
||||
/** @type {RootFile} */
|
||||
const result = { file: source, specifier: identifierName };
|
||||
return result;
|
||||
}
|
||||
|
||||
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 babelAst = AstService.getAst(code, 'swc-to-babel', { filePath: resolvedSourcePath });
|
||||
|
||||
const shouldLookForDefaultExport = identifierName === '[default]';
|
||||
|
||||
let reexportMatch = false; // named specifier declaration
|
||||
let exportMatch;
|
||||
let pendingTrackDownPromise;
|
||||
|
||||
babelTraverse.default(babelAst, {
|
||||
ExportDefaultDeclaration(astPath) {
|
||||
if (!shouldLookForDefaultExport) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newSource;
|
||||
if (astPath.node.declaration.type === 'Identifier') {
|
||||
newSource = getImportSourceFromAst(astPath, astPath.node.declaration.name).source;
|
||||
}
|
||||
|
||||
if (newSource) {
|
||||
pendingTrackDownPromise = trackDownIdentifier(
|
||||
newSource,
|
||||
'[default]',
|
||||
resolvedSourcePath,
|
||||
rootPath,
|
||||
projectName,
|
||||
depth + 1,
|
||||
);
|
||||
} else {
|
||||
// We found our file!
|
||||
rootSpecifier = identifierName;
|
||||
rootFilePath = toRelativeSourcePath(resolvedSourcePath, rootPath);
|
||||
}
|
||||
astPath.stop();
|
||||
},
|
||||
ExportNamedDeclaration: {
|
||||
enter(astPath) {
|
||||
if (reexportMatch || shouldLookForDefaultExport) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Are we dealing with a re-export ?
|
||||
if (astPath.node.specifiers?.length) {
|
||||
exportMatch = astPath.node.specifiers.find(s => s.exported.name === identifierName);
|
||||
|
||||
if (exportMatch) {
|
||||
const localName = exportMatch.local.name;
|
||||
let newSource;
|
||||
if (astPath.node.source) {
|
||||
/**
|
||||
* @example
|
||||
* export { x } from 'y'
|
||||
*/
|
||||
newSource = astPath.node.source.value;
|
||||
} else {
|
||||
/**
|
||||
* @example
|
||||
* import { x } from 'y'
|
||||
* export { x }
|
||||
*/
|
||||
newSource = getImportSourceFromAst(astPath, identifierName).source;
|
||||
|
||||
if (!newSource || newSource === '[current]') {
|
||||
/**
|
||||
* @example
|
||||
* const x = 12;
|
||||
* export { x }
|
||||
*/
|
||||
return;
|
||||
}
|
||||
}
|
||||
reexportMatch = true;
|
||||
pendingTrackDownPromise = trackDownIdentifier(
|
||||
newSource,
|
||||
localName,
|
||||
resolvedSourcePath,
|
||||
rootPath,
|
||||
projectName,
|
||||
depth + 1,
|
||||
);
|
||||
astPath.stop();
|
||||
}
|
||||
}
|
||||
},
|
||||
exit(astPath) {
|
||||
if (!reexportMatch) {
|
||||
// We didn't find a re-exported Identifier, that means the reference is declared
|
||||
// in current file...
|
||||
rootSpecifier = identifierName;
|
||||
rootFilePath = toRelativeSourcePath(resolvedSourcePath, rootPath);
|
||||
|
||||
if (exportMatch) {
|
||||
astPath.stop();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (pendingTrackDownPromise) {
|
||||
// We can't handle promises inside Babel traverse, so we do it here...
|
||||
const resObj = await pendingTrackDownPromise;
|
||||
rootFilePath = resObj.file;
|
||||
rootSpecifier = resObj.specifier;
|
||||
}
|
||||
|
||||
return /** @type { RootFile } */ { file: rootFilePath, specifier: rootSpecifier };
|
||||
}
|
||||
|
||||
trackDownIdentifier = memoize(trackDownIdentifierFn);
|
||||
|
||||
/**
|
||||
* @param {NodePath} astPath
|
||||
* @param {string} identifierNameInScope
|
||||
* @param {PathFromSystemRoot} fullCurrentFilePath
|
||||
* @param {PathFromSystemRoot} projectPath
|
||||
* @param {string} [projectName]
|
||||
*/
|
||||
async function trackDownIdentifierFromScopeFn(
|
||||
astPath,
|
||||
identifierNameInScope,
|
||||
fullCurrentFilePath,
|
||||
projectPath,
|
||||
projectName,
|
||||
) {
|
||||
const sourceObj = getImportSourceFromAst(astPath, identifierNameInScope);
|
||||
|
||||
/** @type {RootFile} */
|
||||
let rootFile;
|
||||
if (sourceObj.source) {
|
||||
rootFile = await trackDownIdentifier(
|
||||
sourceObj.source,
|
||||
sourceObj.importedIdentifierName,
|
||||
fullCurrentFilePath,
|
||||
projectPath,
|
||||
projectName,
|
||||
);
|
||||
} else {
|
||||
const specifier = sourceObj.importedIdentifierName || identifierNameInScope;
|
||||
rootFile = { file: '[current]', specifier };
|
||||
}
|
||||
return rootFile;
|
||||
}
|
||||
|
||||
export const trackDownIdentifierFromScope = memoize(trackDownIdentifierFromScopeFn);
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
import fs from 'fs';
|
||||
import pathLib from 'path';
|
||||
import babelTraverse from '@babel/traverse';
|
||||
import path from 'path';
|
||||
import { swcTraverse } from '../../utils/swc-traverse.js';
|
||||
import { isRelativeSourcePath, toRelativeSourcePath } from '../../utils/relative-source-path.js';
|
||||
import { InputDataService } from '../../core/InputDataService.js';
|
||||
import { resolveImportPath } from '../../utils/resolve-import-path.js';
|
||||
import { AstService } from '../../core/AstService.js';
|
||||
import { LogService } from '../../core/LogService.js';
|
||||
import { memoize } from '../../utils/memoize.js';
|
||||
|
||||
/**
|
||||
|
|
@ -13,7 +12,7 @@ import { memoize } from '../../utils/memoize.js';
|
|||
* @typedef {import('../../../../types/index.js').SpecifierSource} SpecifierSource
|
||||
* @typedef {import('../../../../types/index.js').IdentifierName} IdentifierName
|
||||
* @typedef {import('../../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('@babel/traverse').NodePath} NodePath
|
||||
* @typedef {import('../../../../types/index.js').SwcPath} SwcPath
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -40,7 +39,7 @@ function isExternalProject(source, projectName) {
|
|||
* Other than with import, no binding is created for MyClass by Babel(?)
|
||||
* This means 'path.scope.getBinding('MyClass')' returns undefined
|
||||
* and we have to find a different way to retrieve this value.
|
||||
* @param {NodePath} astPath Babel ast traversal path
|
||||
* @param {SwcPath} astPath Babel ast traversal path
|
||||
* @param {IdentifierName} identifierName the name that should be tracked (and that exists inside scope of astPath)
|
||||
*/
|
||||
function getBindingAndSourceReexports(astPath, identifierName) {
|
||||
|
|
@ -54,18 +53,21 @@ function getBindingAndSourceReexports(astPath, identifierName) {
|
|||
curPath = curPath.parentPath;
|
||||
}
|
||||
const rootPath = curPath;
|
||||
rootPath.traverse({
|
||||
ExportSpecifier(path) {
|
||||
|
||||
swcTraverse(rootPath.node, {
|
||||
ExportSpecifier(astPath) {
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
const found =
|
||||
// @ts-expect-error
|
||||
path.node.exported.name === identifierName || path.node.local.name === identifierName;
|
||||
astPath.node.orig?.value === identifierName ||
|
||||
astPath.node.exported?.value === identifierName ||
|
||||
astPath.node.local?.value === identifierName;
|
||||
if (found) {
|
||||
bindingPath = path;
|
||||
bindingPath = astPath;
|
||||
bindingType = 'ExportSpecifier';
|
||||
// @ts-expect-error
|
||||
source = path.parentPath.node.source ? path.parentPath.node.source.value : '[current]';
|
||||
path.stop();
|
||||
source = astPath.parentPath.node.source
|
||||
? astPath.parentPath.node.source.value
|
||||
: '[current]';
|
||||
astPath.stop();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -78,7 +80,7 @@ function getBindingAndSourceReexports(astPath, identifierName) {
|
|||
* We might be an import that was locally renamed.
|
||||
* Since we are traversing, we are interested in the imported name. Or in case of a re-export,
|
||||
* the local name.
|
||||
* @param {NodePath} astPath Babel ast traversal path
|
||||
* @param {SwcPath} astPath Babel ast traversal path
|
||||
* @param {string} identifierName the name that should be tracked (and that exists inside scope of astPath)
|
||||
* @returns {{ source:string, importedIdentifierName:string }}
|
||||
*/
|
||||
|
|
@ -86,13 +88,12 @@ export function getImportSourceFromAst(astPath, identifierName) {
|
|||
let source;
|
||||
let importedIdentifierName;
|
||||
|
||||
const binding = astPath.scope.getBinding(identifierName);
|
||||
const binding = astPath.scope.bindings[identifierName];
|
||||
let bindingType = binding?.path.type;
|
||||
let bindingPath = binding?.path;
|
||||
const matchingTypes = ['ImportSpecifier', 'ImportDefaultSpecifier', 'ExportSpecifier'];
|
||||
|
||||
if (bindingType && matchingTypes.includes(bindingType)) {
|
||||
// @ts-expect-error
|
||||
source = binding?.path?.parentPath?.node?.source?.value;
|
||||
} else {
|
||||
// no binding
|
||||
|
|
@ -103,10 +104,10 @@ export function getImportSourceFromAst(astPath, identifierName) {
|
|||
if (shouldLookForDefaultExport) {
|
||||
importedIdentifierName = '[default]';
|
||||
} else if (source) {
|
||||
// @ts-expect-error
|
||||
const { node } = bindingPath;
|
||||
importedIdentifierName = (node.imported && node.imported.name) || node.local.name;
|
||||
importedIdentifierName = node.orig?.value || node.imported?.value || node.local?.value;
|
||||
}
|
||||
|
||||
return { source, importedIdentifierName };
|
||||
}
|
||||
|
||||
|
|
@ -174,17 +175,26 @@ async function trackDownIdentifierFn(
|
|||
|
||||
const resolvedSourcePath = await resolveImportPath(source, currentFilePath);
|
||||
|
||||
LogService.debug(`[trackDownIdentifier] ${resolvedSourcePath}`);
|
||||
// if (resolvedSourcePath === null) {
|
||||
// LogService.error(`[trackDownIdentifier] ${resolvedSourcePath} not found`);
|
||||
|
||||
// }
|
||||
// if (resolvedSourcePath === '[node-builtin]') {
|
||||
// LogService.error(`[trackDownIdentifier] ${resolvedSourcePath} not found`);
|
||||
// }
|
||||
|
||||
const allowedJsModuleExtensions = ['.mjs', '.js'];
|
||||
if (!allowedJsModuleExtensions.includes(pathLib.extname(resolvedSourcePath))) {
|
||||
if (
|
||||
!allowedJsModuleExtensions.includes(path.extname(/** @type {string} */ (resolvedSourcePath)))
|
||||
) {
|
||||
// We have an import assertion
|
||||
return /** @type { RootFile } */ {
|
||||
file: toRelativeSourcePath(resolvedSourcePath, rootPath),
|
||||
file: toRelativeSourcePath(/** @type {string} */ (resolvedSourcePath), rootPath),
|
||||
specifier: '[default]',
|
||||
};
|
||||
}
|
||||
const code = fs.readFileSync(resolvedSourcePath, 'utf8');
|
||||
const babelAst = AstService.getAst(code, 'swc-to-babel', { filePath: resolvedSourcePath });
|
||||
const code = fs.readFileSync(/** @type {string} */ (resolvedSourcePath), 'utf8');
|
||||
const swcAst = AstService._getSwcAst(code);
|
||||
|
||||
const shouldLookForDefaultExport = identifierName === '[default]';
|
||||
|
||||
|
|
@ -192,22 +202,24 @@ async function trackDownIdentifierFn(
|
|||
let exportMatch;
|
||||
let pendingTrackDownPromise;
|
||||
|
||||
babelTraverse.default(babelAst, {
|
||||
ExportDefaultDeclaration(path) {
|
||||
const handleExportDefaultDeclOrExpr = astPath => {
|
||||
if (!shouldLookForDefaultExport) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newSource;
|
||||
if (path.node.declaration.type === 'Identifier') {
|
||||
newSource = getImportSourceFromAst(path, path.node.declaration.name).source;
|
||||
if (
|
||||
astPath.node.expression?.type === 'Identifier' ||
|
||||
astPath.node.declaration?.type === 'Identifier'
|
||||
) {
|
||||
newSource = getImportSourceFromAst(astPath, astPath.node.expression.value).source;
|
||||
}
|
||||
|
||||
if (newSource) {
|
||||
pendingTrackDownPromise = trackDownIdentifier(
|
||||
newSource,
|
||||
'[default]',
|
||||
resolvedSourcePath,
|
||||
/** @type {PathFromSystemRoot} */ (resolvedSourcePath),
|
||||
rootPath,
|
||||
projectName,
|
||||
depth + 1,
|
||||
|
|
@ -215,36 +227,41 @@ async function trackDownIdentifierFn(
|
|||
} else {
|
||||
// We found our file!
|
||||
rootSpecifier = identifierName;
|
||||
rootFilePath = toRelativeSourcePath(resolvedSourcePath, rootPath);
|
||||
rootFilePath = toRelativeSourcePath(
|
||||
/** @type {PathFromSystemRoot} */ (resolvedSourcePath),
|
||||
rootPath,
|
||||
);
|
||||
}
|
||||
path.stop();
|
||||
},
|
||||
ExportNamedDeclaration: {
|
||||
enter(path) {
|
||||
astPath.stop();
|
||||
};
|
||||
const handleExportDeclOrNamedDecl = {
|
||||
enter(astPath) {
|
||||
if (reexportMatch || shouldLookForDefaultExport) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Are we dealing with a re-export ?
|
||||
if (path.node.specifiers?.length) {
|
||||
exportMatch = path.node.specifiers.find(s => s.exported.name === identifierName);
|
||||
if (astPath.node.specifiers?.length) {
|
||||
exportMatch = astPath.node.specifiers.find(
|
||||
s => s.orig?.value === identifierName || s.exported?.value === identifierName,
|
||||
);
|
||||
|
||||
if (exportMatch) {
|
||||
const localName = exportMatch.local.name;
|
||||
const localName = exportMatch.orig.value;
|
||||
let newSource;
|
||||
if (path.node.source) {
|
||||
if (astPath.node.source) {
|
||||
/**
|
||||
* @example
|
||||
* export { x } from 'y'
|
||||
*/
|
||||
newSource = path.node.source.value;
|
||||
newSource = astPath.node.source.value;
|
||||
} else {
|
||||
/**
|
||||
* @example
|
||||
* import { x } from 'y'
|
||||
* export { x }
|
||||
*/
|
||||
newSource = getImportSourceFromAst(path, identifierName).source;
|
||||
newSource = getImportSourceFromAst(astPath, identifierName).source;
|
||||
|
||||
if (!newSource || newSource === '[current]') {
|
||||
/**
|
||||
|
|
@ -264,11 +281,11 @@ async function trackDownIdentifierFn(
|
|||
projectName,
|
||||
depth + 1,
|
||||
);
|
||||
path.stop();
|
||||
astPath.stop();
|
||||
}
|
||||
}
|
||||
},
|
||||
exit(path) {
|
||||
exit(astPath) {
|
||||
if (!reexportMatch) {
|
||||
// We didn't find a re-exported Identifier, that means the reference is declared
|
||||
// in current file...
|
||||
|
|
@ -276,12 +293,20 @@ async function trackDownIdentifierFn(
|
|||
rootFilePath = toRelativeSourcePath(resolvedSourcePath, rootPath);
|
||||
|
||||
if (exportMatch) {
|
||||
path.stop();
|
||||
astPath.stop();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const visitor = {
|
||||
ExportDefaultDeclaration: handleExportDefaultDeclOrExpr,
|
||||
ExportDefaultExpression: handleExportDefaultDeclOrExpr,
|
||||
ExportNamedDeclaration: handleExportDeclOrNamedDecl,
|
||||
ExportDeclaration: handleExportDeclOrNamedDecl,
|
||||
};
|
||||
|
||||
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true });
|
||||
|
||||
if (pendingTrackDownPromise) {
|
||||
// We can't handle promises inside Babel traverse, so we do it here...
|
||||
|
|
@ -296,7 +321,7 @@ async function trackDownIdentifierFn(
|
|||
trackDownIdentifier = memoize(trackDownIdentifierFn);
|
||||
|
||||
/**
|
||||
* @param {NodePath} astPath
|
||||
* @param {SwcPath} astPath
|
||||
* @param {string} identifierNameInScope
|
||||
* @param {PathFromSystemRoot} fullCurrentFilePath
|
||||
* @param {PathFromSystemRoot} projectPath
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { transformIntoIterableFindImportsOutput } from './helpers/transform-into
|
|||
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -155,8 +156,9 @@ async function matchImportsPostprocess(exportsAnalyzerResult, importsAnalyzerRes
|
|||
}
|
||||
|
||||
export default class MatchImportsAnalyzer extends Analyzer {
|
||||
/** @type {AnalyzerName} */
|
||||
static analyzerName = 'match-imports';
|
||||
static analyzerName = /** @type {AnalyzerName} */ ('match-imports');
|
||||
|
||||
static requiredAst = /** @type {AnalyzerAst} */ ('swc');
|
||||
|
||||
static requiresReference = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ import { QueryService } from './QueryService.js';
|
|||
import { ReportService } from './ReportService.js';
|
||||
import { InputDataService } from './InputDataService.js';
|
||||
import { toPosixPath } from '../utils/to-posix-path.js';
|
||||
import { memoize } from '../utils/memoize.js';
|
||||
import { getFilePathRelativeFromRoot } from '../utils/get-file-path-relative-from-root.js';
|
||||
|
||||
/**
|
||||
* @typedef {import("@swc/core").Module} SwcAstModule
|
||||
* @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
|
||||
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('../../../types/index.js').QueryOutput} QueryOutput
|
||||
* @typedef {import('../../../types/index.js').ProjectInputData} ProjectInputData
|
||||
|
|
@ -25,12 +26,13 @@ import { getFilePathRelativeFromRoot } from '../utils/get-file-path-relative-fro
|
|||
* Analyzes one entry: the callback can traverse a given ast for each entry
|
||||
* @param {ProjectInputDataWithMeta} projectData
|
||||
* @param {function} astAnalysis
|
||||
* @param {object} analyzerCfg
|
||||
*/
|
||||
async function analyzePerAstFile(projectData, astAnalysis) {
|
||||
async function analyzePerAstFile(projectData, astAnalysis, analyzerCfg) {
|
||||
const entries = [];
|
||||
for (const { file, ast, context: astContext } of projectData.entries) {
|
||||
const relativePath = getFilePathRelativeFromRoot(file, projectData.project.path);
|
||||
const context = { code: astContext.code, relativePath, projectData };
|
||||
const context = { code: astContext.code, relativePath, projectData, analyzerCfg };
|
||||
LogService.debug(`${pathLib.resolve(projectData.project.path, file)}`);
|
||||
const { result, meta } = await astAnalysis(ast, context);
|
||||
entries.push({ file: relativePath, meta, result });
|
||||
|
|
@ -86,8 +88,8 @@ function ensureAnalyzerResultFormat(queryOutput, cfg, analyzer) {
|
|||
const aResult = {
|
||||
queryOutput,
|
||||
analyzerMeta: {
|
||||
name: analyzer.name,
|
||||
requiredAst: analyzer.requiredAst,
|
||||
name: analyzer.constructor.analyzerName,
|
||||
requiredAst: analyzer.constructor.requiredAst,
|
||||
identifier,
|
||||
...optional,
|
||||
configuration: cfg,
|
||||
|
|
@ -131,11 +133,10 @@ function ensureAnalyzerResultFormat(queryOutput, cfg, analyzer) {
|
|||
* @typedef {(referencePath:PathFromSystemRoot,targetPath:PathFromSystemRoot) => {compatible:boolean; reason?:string}} CheckForMatchCompatibilityFn
|
||||
* @type {CheckForMatchCompatibilityFn}
|
||||
*/
|
||||
const checkForMatchCompatibility = memoize(
|
||||
(
|
||||
const checkForMatchCompatibility = (
|
||||
/** @type {PathFromSystemRoot} */ referencePath,
|
||||
/** @type {PathFromSystemRoot} */ targetPath,
|
||||
) => {
|
||||
) => {
|
||||
// const refFile = pathLib.resolve(referencePath, 'package.json');
|
||||
const referencePkg = InputDataService.getPackageJson(referencePath);
|
||||
// const targetFile = pathLib.resolve(targetPath, 'package.json');
|
||||
|
|
@ -153,8 +154,7 @@ const checkForMatchCompatibility = memoize(
|
|||
return { compatible: false, reason: 'no-matched-version' };
|
||||
}
|
||||
return { compatible: true };
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* If in json format, 'unwind' to be compatible for analysis...
|
||||
|
|
@ -169,13 +169,21 @@ function unwindJsonResult(targetOrReferenceProjectResult) {
|
|||
export class Analyzer {
|
||||
static requiresReference = false;
|
||||
|
||||
/** @type {AnalyzerAst} */
|
||||
static requiredAst = 'babel';
|
||||
|
||||
/** @type {AnalyzerName} */
|
||||
static analyzerName = '';
|
||||
|
||||
name = /** @type {typeof Analyzer} */ (this.constructor).analyzerName;
|
||||
|
||||
/** @type {'babel'|'swc-to-babel'} */
|
||||
requiredAst = 'babel';
|
||||
_customConfig = {};
|
||||
|
||||
get config() {
|
||||
return {
|
||||
...this._customConfig,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* In a MatchAnalyzer, two Analyzers (a reference and targer) are run.
|
||||
|
|
@ -335,33 +343,36 @@ export class Analyzer {
|
|||
*/
|
||||
const astDataProjects = await QueryService.addAstToProjectsData(
|
||||
finalTargetData,
|
||||
this.requiredAst,
|
||||
this.constructor.requiredAst,
|
||||
);
|
||||
return analyzePerAstFile(astDataProjects[0], traverseEntryFn);
|
||||
return analyzePerAstFile(astDataProjects[0], traverseEntryFn, this.config);
|
||||
}
|
||||
|
||||
async execute(customConfig = {}) {
|
||||
LogService.debug(`Analyzer "${this.name}": started execute method`);
|
||||
|
||||
const cfg = {
|
||||
targetProjectPath: null,
|
||||
referenceProjectPath: null,
|
||||
suppressNonCriticalLogs: false,
|
||||
...customConfig,
|
||||
};
|
||||
/**
|
||||
* Finds export specifiers and sources
|
||||
* @param {FindExportsConfig} customConfig
|
||||
*/
|
||||
async execute(customConfig) {
|
||||
this._customConfig = customConfig;
|
||||
const cfg = this.config;
|
||||
|
||||
/**
|
||||
* Prepare
|
||||
*/
|
||||
const analyzerResult = this._prepare(cfg);
|
||||
if (analyzerResult) {
|
||||
return analyzerResult;
|
||||
const cachedAnalyzerResult = this._prepare(cfg);
|
||||
if (cachedAnalyzerResult) {
|
||||
return cachedAnalyzerResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse
|
||||
*/
|
||||
const queryOutput = await this._traverse(() => {});
|
||||
const queryOutput = await this._traverse({
|
||||
// @ts-ignore
|
||||
traverseEntryFn: this.constructor.analyzeFile,
|
||||
filePaths: cfg.targetFilePaths,
|
||||
projectPath: cfg.targetProjectPath,
|
||||
});
|
||||
|
||||
/**
|
||||
* Finalize
|
||||
|
|
|
|||
|
|
@ -52,6 +52,29 @@ export class AstService {
|
|||
return guardedSwcToBabel(ast, code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles an array of file paths using swc.
|
||||
* @param {string} code
|
||||
* @param {ParserOptions} parserOptions
|
||||
* @returns {SwcAstModule}
|
||||
*/
|
||||
static _getSwcAst(code, parserOptions = {}) {
|
||||
const ast = swc.parseSync(code, {
|
||||
syntax: 'typescript',
|
||||
target: 'es2022',
|
||||
...parserOptions,
|
||||
});
|
||||
return ast;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compensates for swc span bug: https://github.com/swc-project/swc/issues/1366#issuecomment-1516539812
|
||||
* @returns {number}
|
||||
*/
|
||||
static _getSwcOffset() {
|
||||
return swc.parseSync('').span.end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines all script tags as if it were one js file.
|
||||
* @param {string} htmlCode
|
||||
|
|
@ -77,9 +100,9 @@ export class AstService {
|
|||
/**
|
||||
* Returns the Babel AST
|
||||
* @param { string } code
|
||||
* @param { 'babel'|'swc-to-babel'} astType
|
||||
* @param { 'babel'|'swc-to-babel'|'swc'} astType
|
||||
* @param { {filePath?: PathFromSystemRoot} } options
|
||||
* @returns {File|undefined}
|
||||
* @returns {File|undefined|SwcAstModule}
|
||||
*/
|
||||
// eslint-disable-next-line consistent-return
|
||||
static getAst(code, astType, { filePath } = {}) {
|
||||
|
|
@ -91,6 +114,9 @@ export class AstService {
|
|||
if (astType === 'swc-to-babel') {
|
||||
return this._getSwcToBabelAst(code);
|
||||
}
|
||||
if (astType === 'swc') {
|
||||
return this._getSwcAst(code);
|
||||
}
|
||||
throw new Error(`astType "${astType}" not supported.`);
|
||||
} catch (e) {
|
||||
LogService.error(`Error when parsing "${filePath}":/n${e}`);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import { getFilePathRelativeFromRoot } from '../utils/get-file-path-relative-fro
|
|||
import { toPosixPath } from '../utils/to-posix-path.js';
|
||||
import { memoize } from '../utils/memoize.js';
|
||||
|
||||
// const memoize = fn => fn;
|
||||
|
||||
/**
|
||||
* @typedef {import('../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult
|
||||
* @typedef {import('../../../types/index.js').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
|
||||
|
|
@ -309,13 +311,13 @@ export class InputDataService {
|
|||
try {
|
||||
const pkgJson = getPackageJson(projectPath);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
project.mainEntry = this.__normalizeMainEntry(pkgJson.main || './index.js');
|
||||
project.mainEntry = this.__normalizeMainEntry(pkgJson?.main || './index.js');
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
project.name = pkgJson.name;
|
||||
project.name = pkgJson?.name;
|
||||
// TODO: also add meta info whether we are in a monorepo or not.
|
||||
// We do this by checking whether there is a lerna.json on root level.
|
||||
// eslint-disable-next-line no-empty
|
||||
project.version = pkgJson.version;
|
||||
project.version = pkgJson?.version;
|
||||
} catch (e) {
|
||||
LogService.warn(/** @type {string} */ (e));
|
||||
}
|
||||
|
|
@ -422,6 +424,10 @@ export class InputDataService {
|
|||
.filter(dirPath => fs.lstatSync(dirPath).isDirectory());
|
||||
}
|
||||
|
||||
static set targetProjectPaths(v) {
|
||||
this.__targetProjectPaths = ensureArray(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {PathFromSystemRoot[]} a list of strings representing all entry paths for projects we want to query
|
||||
*/
|
||||
|
|
@ -446,10 +452,6 @@ export class InputDataService {
|
|||
this.__referenceProjectPaths = ensureArray(v);
|
||||
}
|
||||
|
||||
static set targetProjectPaths(v) {
|
||||
this.__targetProjectPaths = ensureArray(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {GatherFilesConfig}
|
||||
*/
|
||||
|
|
@ -620,12 +622,12 @@ export class InputDataService {
|
|||
static getMonoRepoPackages(rootPath) {
|
||||
// [1] Look for npm/yarn workspaces
|
||||
const pkgJson = getPackageJson(rootPath);
|
||||
if (pkgJson && pkgJson.workspaces) {
|
||||
if (pkgJson?.workspaces) {
|
||||
return getPathsFromGlobList(pkgJson.workspaces, rootPath);
|
||||
}
|
||||
// [2] Look for lerna packages
|
||||
const lernaJson = getLernaJson(rootPath);
|
||||
if (lernaJson && lernaJson.packages) {
|
||||
if (lernaJson?.packages) {
|
||||
return getPathsFromGlobList(lernaJson.packages, rootPath);
|
||||
}
|
||||
// TODO: support forward compatibility for npm?
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import path from 'path';
|
|||
import { AstService } from './AstService.js';
|
||||
import { LogService } from './LogService.js';
|
||||
import { getFilePathRelativeFromRoot } from '../utils/get-file-path-relative-from-root.js';
|
||||
import { memoize } from '../utils/memoize.js';
|
||||
import { getCurrentDir } from '../utils/get-current-dir.js';
|
||||
// import { memoize } from '../utils/memoize.js';
|
||||
|
||||
const memoize = fn => fn;
|
||||
|
||||
/**
|
||||
* @typedef {import('./Analyzer.js').Analyzer} Analyzer
|
||||
|
|
@ -20,6 +22,7 @@ import { getCurrentDir } from '../utils/get-current-dir.js';
|
|||
* @typedef {import('../../../types/index.js').ProjectInputData} ProjectInputData
|
||||
* @typedef {import('../../../types/index.js').AnalyzerConfig} AnalyzerConfig
|
||||
* @typedef {import('../../../types/index.js').AnalyzerName} AnalyzerName
|
||||
* @typedef {import('../../../types/index.js').AnalyzerAst} AnalyzerAst
|
||||
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
|
||||
* @typedef {import('../../../types/index.js').GatherFilesConfig} GatherFilesConfig
|
||||
* @typedef {import('../../../types/index.js').AnalyzerQueryResult} AnalyzerQueryResult
|
||||
|
|
@ -239,7 +242,7 @@ export class QueryService {
|
|||
|
||||
/**
|
||||
* @param {ProjectInputData[]} projectsData
|
||||
* @param {'babel'|'swc-to-babel'} requiredAst
|
||||
* @param {AnalyzerAst} requiredAst
|
||||
*/
|
||||
static async addAstToProjectsData(projectsData, requiredAst) {
|
||||
return projectsData.map(projectData => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import fs from 'fs';
|
||||
import pathLib from 'path';
|
||||
import { getHash } from '../utils/get-hash.js';
|
||||
import { memoize } from '../utils/memoize.js';
|
||||
// import { memoize } from '../utils/memoize.js';
|
||||
const memoize = fn => fn;
|
||||
|
||||
/**
|
||||
* @typedef {import('../../../types/index.js').Project} Project
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import babelTraversePkg from '@babel/traverse';
|
||||
import { AstService } from '../core/AstService.js';
|
||||
import { trackDownIdentifier } from '../analyzers/helpers/track-down-identifier.js';
|
||||
import { toPosixPath } from './to-posix-path.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@babel/types').Node} Node
|
||||
* @typedef {import('@babel/traverse').NodePath} NodePath
|
||||
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {{rootPath:PathFromSystemRoot; localPath:PathRelativeFromProjectRoot}} opts
|
||||
* @returns
|
||||
*/
|
||||
export function getFilePathOrExternalSource({ rootPath, localPath }) {
|
||||
if (!localPath.startsWith('.')) {
|
||||
// We are not resolving external files like '@lion/input-amount/x.js',
|
||||
// but we give a 100% score if from and to are same here..
|
||||
return localPath;
|
||||
}
|
||||
return toPosixPath(path.resolve(rootPath, localPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assume we had:
|
||||
* ```js
|
||||
* const x = 88;
|
||||
* const y = x;
|
||||
* export const myIdentifier = y;
|
||||
* ```
|
||||
* - We started in getSourceCodeFragmentOfDeclaration (looking for 'myIdentifier'), which found VariableDeclarator of export myIdentifier
|
||||
* - getReferencedDeclaration is called with { referencedIdentifierName: 'y', ... }
|
||||
* - now we will look in globalScopeBindings, till we find declaration of 'y'
|
||||
* - Is it a ref? Call ourselves with referencedIdentifierName ('x' in example above)
|
||||
* - is it a non ref declaration? Return the path of the node
|
||||
* @param {{ referencedIdentifierName:string, globalScopeBindings:BabelBinding; }} opts
|
||||
* @returns {NodePath}
|
||||
*/
|
||||
export function getReferencedDeclaration({ referencedIdentifierName, globalScopeBindings }) {
|
||||
const [, refDeclaratorBinding] = Object.entries(globalScopeBindings).find(
|
||||
([key]) => key === referencedIdentifierName,
|
||||
);
|
||||
|
||||
if (
|
||||
refDeclaratorBinding.path.type === 'ImportSpecifier' ||
|
||||
refDeclaratorBinding.path.type === 'ImportDefaultSpecifier'
|
||||
) {
|
||||
return refDeclaratorBinding.path;
|
||||
}
|
||||
|
||||
if (refDeclaratorBinding.path.node.init.type === 'Identifier') {
|
||||
return getReferencedDeclaration({
|
||||
referencedIdentifierName: refDeclaratorBinding.path.node.init.name,
|
||||
globalScopeBindings,
|
||||
});
|
||||
}
|
||||
|
||||
return refDeclaratorBinding.path.get('init');
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```js
|
||||
* // ------ input file --------
|
||||
* const x = 88;
|
||||
* const y = x;
|
||||
* export const myIdentifier = y;
|
||||
* // --------------------------
|
||||
*
|
||||
* await getSourceCodeFragmentOfDeclaration(code) // finds "88"
|
||||
* ```
|
||||
*
|
||||
* @param {{ filePath: PathFromSystemRoot; exportedIdentifier: string; projectRootPath: PathFromSystemRoot }} opts
|
||||
* @returns {Promise<{ sourceNodePath: string; sourceFragment: string|null; externalImportSource: string; }>}
|
||||
*/
|
||||
export async function getSourceCodeFragmentOfDeclaration({
|
||||
filePath,
|
||||
exportedIdentifier,
|
||||
projectRootPath,
|
||||
}) {
|
||||
const code = fs.readFileSync(filePath, 'utf8');
|
||||
// TODO: fix swc-to-babel lib to make this compatible with 'swc-to-babel' mode of getAst
|
||||
const babelAst = AstService.getAst(code, 'babel', { filePath });
|
||||
|
||||
/** @type {NodePath} */
|
||||
let finalNodePath;
|
||||
|
||||
babelTraversePkg.default(babelAst, {
|
||||
Program(astPath) {
|
||||
astPath.stop();
|
||||
|
||||
// Situations
|
||||
// - Identifier is part of default export (in this case 'exportedIdentifier' is '[default]' )
|
||||
// - declared right away (for instance a class)
|
||||
// - referenced (possibly recursively) by other declaration
|
||||
// - Identifier is part of a named export
|
||||
// - declared right away
|
||||
// - referenced (possibly recursively) by other declaration
|
||||
|
||||
const globalScopeBindings = astPath.get('body')[0].scope.bindings;
|
||||
|
||||
if (exportedIdentifier === '[default]') {
|
||||
const defaultExportPath = astPath
|
||||
.get('body')
|
||||
.find(child => child.node.type === 'ExportDefaultDeclaration');
|
||||
// @ts-expect-error
|
||||
const isReferenced = defaultExportPath?.node.declaration?.type === 'Identifier';
|
||||
|
||||
if (!isReferenced) {
|
||||
finalNodePath = defaultExportPath.get('declaration');
|
||||
} else {
|
||||
finalNodePath = getReferencedDeclaration({
|
||||
referencedIdentifierName: defaultExportPath.node.declaration.name,
|
||||
globalScopeBindings,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const variableDeclaratorPath = astPath.scope.getBinding(exportedIdentifier).path;
|
||||
const varDeclNode = variableDeclaratorPath.node;
|
||||
const isReferenced = varDeclNode.init?.type === 'Identifier';
|
||||
const contentPath = varDeclNode.init
|
||||
? variableDeclaratorPath.get('init')
|
||||
: variableDeclaratorPath;
|
||||
|
||||
const name = varDeclNode.init
|
||||
? varDeclNode.init.name
|
||||
: varDeclNode.id?.name || varDeclNode.imported.name;
|
||||
|
||||
if (!isReferenced) {
|
||||
// it must be an exported declaration
|
||||
finalNodePath = contentPath;
|
||||
} else {
|
||||
finalNodePath = getReferencedDeclaration({
|
||||
referencedIdentifierName: name,
|
||||
globalScopeBindings,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
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,
|
||||
);
|
||||
const filePathOrSrc = getFilePathOrExternalSource({
|
||||
rootPath: projectRootPath,
|
||||
localPath: rootFile.file,
|
||||
});
|
||||
|
||||
// TODO: allow resolving external project file paths
|
||||
if (!filePathOrSrc.startsWith('/')) {
|
||||
// So we have external project; smth like '@lion/input/x.js'
|
||||
return {
|
||||
sourceNodePath: finalNodePath,
|
||||
sourceFragment: null,
|
||||
externalImportSource: filePathOrSrc,
|
||||
};
|
||||
}
|
||||
|
||||
return getSourceCodeFragmentOfDeclaration({
|
||||
filePath: filePathOrSrc,
|
||||
exportedIdentifier: rootFile.specifier,
|
||||
projectRootPath,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
sourceNodePath: finalNodePath,
|
||||
sourceFragment: code.slice(
|
||||
finalNodePath.node?.loc?.start.index,
|
||||
finalNodePath.node?.loc?.end.index,
|
||||
),
|
||||
externalImportSource: null,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import babelTraversePkg from '@babel/traverse';
|
||||
import { swcTraverse, getPathFromNode } from './swc-traverse.js';
|
||||
import { AstService } from '../core/AstService.js';
|
||||
import { trackDownIdentifier } from '../analyzers/helpers/track-down-identifier.js';
|
||||
import { toPosixPath } from './to-posix-path.js';
|
||||
|
|
@ -8,6 +8,7 @@ import { toPosixPath } from './to-posix-path.js';
|
|||
/**
|
||||
* @typedef {import('@babel/types').Node} Node
|
||||
* @typedef {import('@babel/traverse').NodePath} NodePath
|
||||
* @typedef {import('../../../types/index.js').SwcBinding} SwcBinding
|
||||
* @typedef {import('../../../types/index.js').PathRelativeFromProjectRoot} PathRelativeFromProjectRoot
|
||||
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
|
||||
*/
|
||||
|
|
@ -33,28 +34,32 @@ export function getFilePathOrExternalSource({ rootPath, localPath }) {
|
|||
* export const myIdentifier = y;
|
||||
* ```
|
||||
* - We started in getSourceCodeFragmentOfDeclaration (looking for 'myIdentifier'), which found VariableDeclarator of export myIdentifier
|
||||
* - getReferencedDeclaration is called with { referencedIdentifierName: 'y', ... }
|
||||
* - getReferencedDeclaration is called with { referencedIdentifierName: 'y', globalScopeBindings: {x: SwcBinding; y: SwcBinding} }
|
||||
* - now we will look in globalScopeBindings, till we find declaration of 'y'
|
||||
* - Is it a ref? Call ourselves with referencedIdentifierName ('x' in example above)
|
||||
* - is it a non ref declaration? Return the path of the node
|
||||
* @param {{ referencedIdentifierName:string, globalScopeBindings:BabelBinding; }} opts
|
||||
* @param {{ referencedIdentifierName:string, globalScopeBindings:{[key:string]:SwcBinding}; }} opts
|
||||
* @returns {NodePath}
|
||||
*/
|
||||
export function getReferencedDeclaration({ referencedIdentifierName, globalScopeBindings }) {
|
||||
const [, refDeclaratorBinding] = Object.entries(globalScopeBindings).find(
|
||||
([key]) => key === referencedIdentifierName,
|
||||
);
|
||||
// We go from referencedIdentifierName 'y' to binding (VariableDeclarator path) 'y';
|
||||
// const [, refDeclaratorBinding] =
|
||||
// Object.entries(globalScopeBindings).find(([key]) => key === referencedIdentifierName) || [];
|
||||
|
||||
if (
|
||||
refDeclaratorBinding.path.type === 'ImportSpecifier' ||
|
||||
refDeclaratorBinding.path.type === 'ImportDefaultSpecifier'
|
||||
) {
|
||||
const refDeclaratorBinding = globalScopeBindings[referencedIdentifierName];
|
||||
|
||||
// We provided a referencedIdentifierName that is not in the globalScopeBindings
|
||||
if (!refDeclaratorBinding) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (['ImportSpecifier', 'ImportDefaultSpecifier'].includes(refDeclaratorBinding.path.node.type)) {
|
||||
return refDeclaratorBinding.path;
|
||||
}
|
||||
|
||||
if (refDeclaratorBinding.path.node.init.type === 'Identifier') {
|
||||
if (refDeclaratorBinding.identifier.init.type === 'Identifier') {
|
||||
return getReferencedDeclaration({
|
||||
referencedIdentifierName: refDeclaratorBinding.path.node.init.name,
|
||||
referencedIdentifierName: refDeclaratorBinding.identifier.init.value,
|
||||
globalScopeBindings,
|
||||
});
|
||||
}
|
||||
|
|
@ -83,14 +88,19 @@ export async function getSourceCodeFragmentOfDeclaration({
|
|||
projectRootPath,
|
||||
}) {
|
||||
const code = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// compensate for swc span bug: https://github.com/swc-project/swc/issues/1366#issuecomment-1516539812
|
||||
const offset = AstService._getSwcOffset();
|
||||
// TODO: fix swc-to-babel lib to make this compatible with 'swc-to-babel' mode of getAst
|
||||
const babelAst = AstService.getAst(code, 'babel', { filePath });
|
||||
const swcAst = AstService._getSwcAst(code);
|
||||
|
||||
/** @type {NodePath} */
|
||||
let finalNodePath;
|
||||
|
||||
babelTraversePkg.default(babelAst, {
|
||||
Program(astPath) {
|
||||
swcTraverse(
|
||||
swcAst,
|
||||
{
|
||||
Module(astPath) {
|
||||
astPath.stop();
|
||||
|
||||
// Situations
|
||||
|
|
@ -101,25 +111,26 @@ export async function getSourceCodeFragmentOfDeclaration({
|
|||
// - declared right away
|
||||
// - referenced (possibly recursively) by other declaration
|
||||
|
||||
const globalScopeBindings = astPath.get('body')[0].scope.bindings;
|
||||
const globalScopeBindings = getPathFromNode(astPath.node.body?.[0])?.scope.bindings;
|
||||
|
||||
if (exportedIdentifier === '[default]') {
|
||||
const defaultExportPath = astPath
|
||||
.get('body')
|
||||
.find(child => child.node.type === 'ExportDefaultDeclaration');
|
||||
// @ts-expect-error
|
||||
const isReferenced = defaultExportPath?.node.declaration?.type === 'Identifier';
|
||||
const defaultExportPath = getPathFromNode(
|
||||
astPath.node.body.find(child =>
|
||||
['ExportDefaultDeclaration', 'ExportDefaultExpression'].includes(child.type),
|
||||
),
|
||||
);
|
||||
const isReferenced = defaultExportPath?.node.expression?.type === 'Identifier';
|
||||
|
||||
if (!isReferenced) {
|
||||
finalNodePath = defaultExportPath.get('declaration');
|
||||
finalNodePath = defaultExportPath.get('decl') || defaultExportPath.get('expression');
|
||||
} else {
|
||||
finalNodePath = getReferencedDeclaration({
|
||||
referencedIdentifierName: defaultExportPath.node.declaration.name,
|
||||
referencedIdentifierName: defaultExportPath.node.expression.value,
|
||||
globalScopeBindings,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const variableDeclaratorPath = astPath.scope.getBinding(exportedIdentifier).path;
|
||||
const variableDeclaratorPath = astPath.scope.bindings[exportedIdentifier].path;
|
||||
const varDeclNode = variableDeclaratorPath.node;
|
||||
const isReferenced = varDeclNode.init?.type === 'Identifier';
|
||||
const contentPath = varDeclNode.init
|
||||
|
|
@ -127,8 +138,8 @@ export async function getSourceCodeFragmentOfDeclaration({
|
|||
: variableDeclaratorPath;
|
||||
|
||||
const name = varDeclNode.init
|
||||
? varDeclNode.init.name
|
||||
: varDeclNode.id?.name || varDeclNode.imported.name;
|
||||
? varDeclNode.init.value
|
||||
: varDeclNode.id?.value || varDeclNode.imported?.value || varDeclNode.orig?.value;
|
||||
|
||||
if (!isReferenced) {
|
||||
// it must be an exported declaration
|
||||
|
|
@ -141,12 +152,14 @@ export async function getSourceCodeFragmentOfDeclaration({
|
|||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
{ needsAdvancedPaths: true },
|
||||
);
|
||||
|
||||
if (finalNodePath.type === 'ImportSpecifier') {
|
||||
const importDeclNode = finalNodePath.parentPath.node;
|
||||
const source = importDeclNode.source.value;
|
||||
const identifierName = finalNodePath.node.imported.name;
|
||||
const identifierName = finalNodePath.node.imported?.value || finalNodePath.node.local?.value;
|
||||
const currentFilePath = filePath;
|
||||
|
||||
const rootFile = await trackDownIdentifier(
|
||||
|
|
@ -180,9 +193,10 @@ export async function getSourceCodeFragmentOfDeclaration({
|
|||
return {
|
||||
sourceNodePath: finalNodePath,
|
||||
sourceFragment: code.slice(
|
||||
finalNodePath.node?.loc?.start.index,
|
||||
finalNodePath.node?.loc?.end.index,
|
||||
finalNodePath.node?.span?.start - 1 - offset,
|
||||
finalNodePath.node?.span?.end - 1 - offset,
|
||||
),
|
||||
// sourceFragment: finalNodePath.node?.raw || finalNodePath.node?.value,
|
||||
externalImportSource: null,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,9 +66,9 @@ async function resolveImportPathFn(importee, importer, opts) {
|
|||
);
|
||||
|
||||
if (!result?.id) {
|
||||
LogService.warn(
|
||||
`[resolveImportPath] importee ${importee} not found in filesystem for importer '${importer}'.`,
|
||||
);
|
||||
// LogService.warn(
|
||||
// `[resolveImportPath] importee ${importee} not found in filesystem for importer '${importer}'.`,
|
||||
// );
|
||||
return null;
|
||||
}
|
||||
return toPosixPath(result.id);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,359 @@
|
|||
/**
|
||||
* @typedef {import('@swc/core').Module} SwcAstModule
|
||||
* @typedef {import('@swc/core').Node} SwcNode
|
||||
* @typedef {import('@swc/core').VariableDeclarator} SwcVariableDeclarator
|
||||
* @typedef {import('@swc/core').Identifier} SwcIdentifierNode
|
||||
* @typedef {import('../../../types/index.js').SwcPath} SwcPath
|
||||
* @typedef {import('../../../types/index.js').SwcScope} SwcScope
|
||||
* @typedef {import('../../../types/index.js').SwcVisitor} SwcVisitor
|
||||
* @typedef {import('../../../types/index.js').SwcBinding} SwcBinding
|
||||
* @typedef {import('../../../types/index.js').SwcTraversalContext} SwcTraversalContext
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains all node info, to create paths from
|
||||
* @type {WeakMap<SwcNode,SwcPath>}
|
||||
*/
|
||||
const swcPathCache = new WeakMap();
|
||||
|
||||
const fnTypes = [
|
||||
'FunctionDeclaration',
|
||||
'FunctionExpression',
|
||||
'ArrowFunctionExpression',
|
||||
'ClassMethod',
|
||||
'Constructor',
|
||||
];
|
||||
|
||||
const nonBlockParentTypes = [...fnTypes, 'SwitchStatement', 'ClassDeclaration'];
|
||||
|
||||
/**
|
||||
* @param {SwcPath} swcPath
|
||||
* @param {SwcScope} currentScope
|
||||
* @param {SwcTraversalContext} traversalContext
|
||||
* @returns {SwcScope|null}
|
||||
*/
|
||||
function getNewScope(swcPath, currentScope, traversalContext) {
|
||||
const { node, parent } = swcPath;
|
||||
// const hasNonBlockParent = (/** @type {SwcNode} */ nd) => nonBlockParentTypes.includes(nd.type);
|
||||
const isFn = (/** @type {SwcNode} */ nd) => nd && fnTypes.includes(nd.type);
|
||||
|
||||
const isIsolatedBlockStatement = !isFn(parent) && node.type === 'BlockStatement';
|
||||
|
||||
// Create new scope...
|
||||
if (nonBlockParentTypes.includes(node.type) || isIsolatedBlockStatement) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
traversalContext.scopeId += 1;
|
||||
return {
|
||||
id: traversalContext.scopeId,
|
||||
parentScope: currentScope,
|
||||
path: swcPath,
|
||||
bindings: {},
|
||||
_pendingRefsWithoutBinding: [],
|
||||
_isIsolatedBlockStatement: isIsolatedBlockStatement,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SwcNode} node
|
||||
*/
|
||||
export function getPathFromNode(node) {
|
||||
return swcPathCache.get(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SwcNode} node
|
||||
* @param {SwcNode|null} parent
|
||||
* @param {Function} stop
|
||||
* @param {SwcScope} [scope]
|
||||
* @returns {SwcPath}
|
||||
*/
|
||||
function createSwcPath(node, parent, stop, scope) {
|
||||
const swcPath = {
|
||||
node,
|
||||
parent,
|
||||
stop,
|
||||
// TODO: "pre-traverse" the missing scope parts instead via getter that adds refs and bindings for current scope
|
||||
scope,
|
||||
parentPath: parent ? getPathFromNode(parent) : null,
|
||||
get(/** @type {string} */ name) {
|
||||
const swcPathForNode = getPathFromNode(node[name]);
|
||||
if (node[name] && !swcPathForNode) {
|
||||
// throw new Error(
|
||||
// `[swcTraverse]: Use {needsAdvancedPaths: true} to find path for node: ${node[name]}`,
|
||||
// );
|
||||
// TODO: "pre-traverse" the missing path parts instead
|
||||
}
|
||||
return swcPathForNode;
|
||||
},
|
||||
get type() {
|
||||
return node.type;
|
||||
},
|
||||
};
|
||||
swcPathCache.set(node, swcPath);
|
||||
return swcPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the node:
|
||||
* - a declaration (like "const a = 1")?
|
||||
* - an import specifier (like "import { a } from 'b'")?
|
||||
* Handy to know if the parents of Identifiers mark a binding
|
||||
* @param {SwcNode} parent
|
||||
* @param {string} identifierValue
|
||||
*/
|
||||
function isBindingNode(parent, identifierValue) {
|
||||
if (parent.type === 'VariableDeclarator') {
|
||||
// @ts-ignore
|
||||
return parent.id.value === identifierValue;
|
||||
}
|
||||
return [
|
||||
'ClassDeclaration',
|
||||
'FunctionDeclaration',
|
||||
'ArrowFunctionExpression',
|
||||
'ImportSpecifier',
|
||||
'ImportDefaultSpecifier',
|
||||
].includes(parent.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the node:
|
||||
* - a declaration (like "const a = 1")?
|
||||
* - an import specifier (like "import { a } from 'b'")?
|
||||
* Handy to know if the parents of Identifiers mark a binding
|
||||
* @param {SwcNode} parent
|
||||
*/
|
||||
function isBindingRefNode(parent) {
|
||||
return ![
|
||||
'ClassMethod',
|
||||
'Constructor',
|
||||
'MemberExpression',
|
||||
'KeyValueProperty',
|
||||
'SwitchStatement',
|
||||
'MethodProperty',
|
||||
].includes(parent.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SwcPath} swcPathForIdentifier
|
||||
* @returns {void}
|
||||
*/
|
||||
function addPotentialBindingOrRefToScope(swcPathForIdentifier) {
|
||||
const { node, parent, scope, parentPath } = swcPathForIdentifier;
|
||||
|
||||
if (node.type !== 'Identifier') {
|
||||
return;
|
||||
}
|
||||
|
||||
// const parentPath = getPathFromNode(parent);
|
||||
if (isBindingNode(parent, node.value)) {
|
||||
/** @type {SwcBinding} */
|
||||
const binding = {
|
||||
identifier: parent,
|
||||
// kind: 'var',
|
||||
refs: [],
|
||||
path: swcPathForIdentifier.parentPath,
|
||||
};
|
||||
let scopeBindingBelongsTo = scope;
|
||||
const isVarInIsolatedBlock =
|
||||
scope._isIsolatedBlockStatement &&
|
||||
swcPathForIdentifier.parentPath.parentPath.node.kind === 'var';
|
||||
const hasNonBlockParent = nonBlockParentTypes.includes(parent.type);
|
||||
|
||||
if (isVarInIsolatedBlock || hasNonBlockParent) {
|
||||
scopeBindingBelongsTo = scope.parentScope || scope;
|
||||
}
|
||||
if (scopeBindingBelongsTo._pendingRefsWithoutBinding.includes(parentPath)) {
|
||||
binding.refs.push(parentPath);
|
||||
scopeBindingBelongsTo._pendingRefsWithoutBinding.splice(
|
||||
scopeBindingBelongsTo._pendingRefsWithoutBinding.indexOf(parentPath),
|
||||
1,
|
||||
);
|
||||
}
|
||||
const idName = node.value || node.local?.value || node.orig?.value;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
scopeBindingBelongsTo.bindings[idName] = binding;
|
||||
|
||||
// Align with Babel... => in example `class Q {}`, Q has binding to root scope and ClassDeclaration scope
|
||||
if (parent.type === 'ClassDeclaration') {
|
||||
scope.bindings[idName] = binding;
|
||||
}
|
||||
}
|
||||
// In other cases, we are dealing with a reference that must be bound to a binding
|
||||
else if (isBindingRefNode(parent)) {
|
||||
const binding = scope.bindings[node.value];
|
||||
if (binding) {
|
||||
binding.refs.push(parentPath);
|
||||
} else {
|
||||
// we are referencing a variable that is not declared in this scope or any parent scope
|
||||
// It might be hoisted, so we might find it later. For now, store it as a pending reference
|
||||
scope._pendingRefsWithoutBinding.push(parentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the node is the root of the ast?
|
||||
* in Babel, this is the equivalent of Program
|
||||
* @param {SwcNode} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isRootNode(node) {
|
||||
return node.type === 'Module' || node.type === 'Script';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{node: SwcNode; }} node
|
||||
* @param {(data:{child:SwcNode}) => void} callback
|
||||
*/
|
||||
const loopChildren = ({ node }, callback) => {
|
||||
for (const [childKey, childVal] of Object.entries(node)) {
|
||||
if (childKey === 'span') {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(childVal)) {
|
||||
for (const childValElem of childVal) {
|
||||
callback({ child: childValElem });
|
||||
}
|
||||
} else if (typeof childVal === 'object') {
|
||||
callback({ child: childVal });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {SwcPath} swcPath
|
||||
* @param {SwcVisitor} visitor
|
||||
* @param {SwcTraversalContext} traversalContext
|
||||
*/
|
||||
function visit(swcPath, visitor, traversalContext) {
|
||||
if (visitor.enter) {
|
||||
// @ts-expect-error
|
||||
visitor.enter(swcPath);
|
||||
}
|
||||
|
||||
if (isRootNode(swcPath.node) && visitor.root) {
|
||||
// @ts-expect-error
|
||||
visitor.root(swcPath);
|
||||
}
|
||||
|
||||
// Later, consider https://github.com/babel/babel/blob/b1e73d6f961065c56427ffa89c130beea8321d3b/packages/babel-traverse/src/traverse-node.ts#L28
|
||||
if (typeof visitor[swcPath.node.type] === 'function') {
|
||||
// @ts-expect-error
|
||||
visitor[swcPath.node.type](swcPath);
|
||||
}
|
||||
// @ts-expect-error
|
||||
else if (visitor[swcPath.node.type]?.enter) {
|
||||
// @ts-expect-error
|
||||
visitor[swcPath.node.type].enter(swcPath);
|
||||
}
|
||||
// @ts-expect-error
|
||||
if (visitor[swcPath.node.type]?.exit) {
|
||||
// Let visitTree know that we should visit on exit
|
||||
// @ts-expect-error
|
||||
traversalContext.visitOnExitFns.push(() => visitor[swcPath.node.type].exit(swcPath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple traversal for swc ast.
|
||||
* @param {SwcAstModule} swcAst
|
||||
* @param {SwcVisitor} visitor
|
||||
* @param {object} config
|
||||
* @param {boolean} [config.needsAdvancedPaths] needs a full traversal before starting the visitor, which is less performant. Only enable when path.get() is used
|
||||
*/
|
||||
export function swcTraverse(swcAst, visitor, { needsAdvancedPaths = false } = {}) {
|
||||
/**
|
||||
* For performance, the author of a visitor can call this to stop further traversal
|
||||
*/
|
||||
let isStopped = false;
|
||||
const stop = () => {
|
||||
isStopped = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {SwcNode} node
|
||||
* @param {SwcNode|null} parent
|
||||
* @param {SwcScope} scope
|
||||
* @param {boolean} hasPreparedTree
|
||||
* @param {SwcTraversalContext} traversalContext
|
||||
*/
|
||||
const handlePathAndScope = (node, parent, scope, hasPreparedTree, traversalContext) => {
|
||||
if (hasPreparedTree) {
|
||||
const swcPath = /** @type {SwcPath} */ (swcPathCache.get(node));
|
||||
return {
|
||||
swcPath,
|
||||
newOrCurScope: getNewScope(swcPath, scope, traversalContext) || scope,
|
||||
};
|
||||
}
|
||||
// `needsAdvancedPaths` was false
|
||||
const swcPath = createSwcPath(node, parent, stop);
|
||||
// We create scopes ourselves, since paths are not prepared yet...
|
||||
const newOrCurScope = getNewScope(swcPath, scope, traversalContext) || scope;
|
||||
swcPath.scope = newOrCurScope;
|
||||
addPotentialBindingOrRefToScope(swcPath);
|
||||
return { newOrCurScope, swcPath };
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {SwcNode} node
|
||||
* @param {SwcNode|null} parent
|
||||
* @param {SwcScope} scope
|
||||
* @param {SwcTraversalContext} traversalContext
|
||||
* @param {{haltCondition?: (node: SwcNode) => boolean;}} [config]
|
||||
*/
|
||||
const prepareTree = (node, parent, scope, traversalContext, { haltCondition } = {}) => {
|
||||
if (!node?.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { newOrCurScope } = handlePathAndScope(node, parent, scope, false, traversalContext);
|
||||
loopChildren({ node }, ({ child }) => {
|
||||
prepareTree(child, node, newOrCurScope, traversalContext, { haltCondition });
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {SwcNode} node
|
||||
* @param {SwcNode|null} parent
|
||||
* @param {SwcScope} scope
|
||||
* @param {{hasPreparedTree?: boolean;}} config
|
||||
* @param {SwcTraversalContext} traversalContext
|
||||
*/
|
||||
const visitTree = (node, parent, scope, config, traversalContext) => {
|
||||
if (!node?.type || isStopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { hasPreparedTree = false } = config || {};
|
||||
|
||||
const { swcPath } = handlePathAndScope(node, parent, scope, hasPreparedTree, traversalContext);
|
||||
visit(swcPath, visitor, traversalContext);
|
||||
loopChildren({ node }, ({ child }) => {
|
||||
visitTree(child, node, swcPath.scope, config, traversalContext);
|
||||
});
|
||||
};
|
||||
|
||||
const traversalContext = { visitOnExitFns: [], scopeId: 0 };
|
||||
// https://developer.mozilla.org/en-US/docs/Glossary/Scope
|
||||
/** @type {SwcScope} */
|
||||
const initialScope = {
|
||||
id: traversalContext.scopeId,
|
||||
bindings: {},
|
||||
path: null,
|
||||
_pendingRefsWithoutBinding: [],
|
||||
_isIsolatedBlockStatement: false,
|
||||
};
|
||||
if (needsAdvancedPaths) {
|
||||
// Do one full traversal to prepare advanced path functionality like path.get() and path.scope.bindings
|
||||
// TODO: improve with on the fly, partial tree traversal for best performance
|
||||
prepareTree(swcAst, null, initialScope, traversalContext);
|
||||
}
|
||||
visitTree(swcAst, null, initialScope, { hasPreparedTree: needsAdvancedPaths }, traversalContext);
|
||||
// @ts-ignore
|
||||
traversalContext.visitOnExitFns.reverse().forEach(fn => fn());
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ import { cli } from '../../src/cli/cli.js';
|
|||
import { _promptAnalyzerMenuModule } from '../../src/cli/prompt-analyzer-menu.js';
|
||||
import { memoizeConfig } from '../../src/program/utils/memoize.js';
|
||||
import { _extendDocsModule } from '../../src/cli/launch-providence-with-extend-docs.js';
|
||||
import { dashboardServer } from '../../dashboard/server.js';
|
||||
import { dashboardServer } from '../../src/dashboard/server.js';
|
||||
import { setupAnalyzerTest } from '../../test-helpers/setup-analyzer-test.js';
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ import pathLib from 'path';
|
|||
import sinon from 'sinon';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { expect } from 'chai';
|
||||
import { it } from 'mocha';
|
||||
import fetch from 'node-fetch';
|
||||
import { createTestServer } from '@web/dev-server-core/test-helpers';
|
||||
import { createDashboardServerConfig } from '../../dashboard/server.js';
|
||||
import { createDashboardServerConfig } from '../../src/dashboard/server.js';
|
||||
import { ReportService } from '../../src/program/core/ReportService.js';
|
||||
import { providenceConfUtil } from '../../src/program/utils/providence-conf-util.js';
|
||||
|
||||
|
|
@ -57,7 +58,7 @@ describe('Dashboard Server', () => {
|
|||
|
||||
describe('Index', () => {
|
||||
it(`returns an index on '/'`, async () => {
|
||||
const response = await fetch(`${host}/dashboard`);
|
||||
const response = await fetch(`${host}/src/dashboard`);
|
||||
const responseText = await response.text();
|
||||
expect(response.status).to.equal(200);
|
||||
expect(responseText).to.include('<title>Providence dashboard</title>');
|
||||
|
|
@ -66,7 +67,7 @@ describe('Dashboard Server', () => {
|
|||
|
||||
describe('App assets', () => {
|
||||
it(`returns (static) js assets via app/*`, async () => {
|
||||
const response = await fetch(`${host}/dashboard/app/p-board.js`);
|
||||
const response = await fetch(`${host}/src/dashboard/app/p-board.js`);
|
||||
expect(response.status).to.equal(200);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,220 @@
|
|||
{
|
||||
"meta": {
|
||||
"searchType": "ast-analyzer",
|
||||
"analyzerMeta": {
|
||||
"name": "find-classes",
|
||||
"requiredAst": "babel",
|
||||
"identifier": "importing-target-project_0.0.2-target-mock__-905964591",
|
||||
"targetProject": {
|
||||
"mainEntry": "./target-src/match-imports/root-level-imports.js",
|
||||
"name": "importing-target-project",
|
||||
"version": "0.0.2-target-mock",
|
||||
"commitHash": "[not-a-git-root]"
|
||||
},
|
||||
"configuration": {
|
||||
"gatherFilesConfig": {},
|
||||
"skipCheckMatchCompatibility": false,
|
||||
"addSystemPathsInResult": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"queryOutput": [
|
||||
{
|
||||
"file": "./target-src/find-customelements/multiple.js",
|
||||
"result": [
|
||||
{
|
||||
"name": null,
|
||||
"isMixin": true,
|
||||
"superClasses": [
|
||||
{
|
||||
"name": "HTMLElement",
|
||||
"isMixin": false,
|
||||
"rootFile": {
|
||||
"file": "[current]",
|
||||
"specifier": "HTMLElement"
|
||||
}
|
||||
}
|
||||
],
|
||||
"members": {
|
||||
"props": [],
|
||||
"methods": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ExtendedOnTheFly",
|
||||
"isMixin": false,
|
||||
"superClasses": [
|
||||
{
|
||||
"isMixin": true,
|
||||
"rootFile": {
|
||||
"file": "[current]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"isMixin": false,
|
||||
"rootFile": {
|
||||
"file": "[current]"
|
||||
}
|
||||
}
|
||||
],
|
||||
"members": {
|
||||
"props": [],
|
||||
"methods": []
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "./target-src/match-subclasses/ExtendedComp.js",
|
||||
"result": [
|
||||
{
|
||||
"name": "ExtendedComp",
|
||||
"isMixin": false,
|
||||
"superClasses": [
|
||||
{
|
||||
"name": "MyCompMixin",
|
||||
"isMixin": true,
|
||||
"rootFile": {
|
||||
"file": "exporting-ref-project",
|
||||
"specifier": "[default]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "RefClass",
|
||||
"isMixin": false,
|
||||
"rootFile": {
|
||||
"file": "exporting-ref-project",
|
||||
"specifier": "RefClass"
|
||||
}
|
||||
}
|
||||
],
|
||||
"members": {
|
||||
"props": [
|
||||
{
|
||||
"name": "getterSetter",
|
||||
"accessType": "public",
|
||||
"kind": [
|
||||
"get",
|
||||
"set"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "staticGetterSetter",
|
||||
"accessType": "public",
|
||||
"static": true,
|
||||
"kind": [
|
||||
"get",
|
||||
"set"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "attributes",
|
||||
"accessType": "public",
|
||||
"static": true,
|
||||
"kind": [
|
||||
"get"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "styles",
|
||||
"accessType": "public",
|
||||
"static": true,
|
||||
"kind": [
|
||||
"get"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "updateComplete",
|
||||
"accessType": "public",
|
||||
"kind": [
|
||||
"get"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "localizeNamespaces",
|
||||
"accessType": "public",
|
||||
"static": true,
|
||||
"kind": [
|
||||
"get"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "slots",
|
||||
"accessType": "public",
|
||||
"kind": [
|
||||
"get"
|
||||
]
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"name": "method",
|
||||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "_protectedMethod",
|
||||
"accessType": "protected"
|
||||
},
|
||||
{
|
||||
"name": "__privateMethod",
|
||||
"accessType": "private"
|
||||
},
|
||||
{
|
||||
"name": "$protectedMethod",
|
||||
"accessType": "protected"
|
||||
},
|
||||
{
|
||||
"name": "$$privateMethod",
|
||||
"accessType": "private"
|
||||
},
|
||||
{
|
||||
"name": "constructor",
|
||||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "connectedCallback",
|
||||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "disconnectedCallback",
|
||||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "requestUpdate",
|
||||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "createRenderRoot",
|
||||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "render",
|
||||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "updated",
|
||||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "firstUpdated",
|
||||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "update",
|
||||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "shouldUpdate",
|
||||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "onLocaleUpdated",
|
||||
"accessType": "public"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"meta": {
|
||||
"searchType": "ast-analyzer",
|
||||
"analyzerMeta": {
|
||||
"name": "find-customelements",
|
||||
"requiredAst": "swc-to-babel",
|
||||
"identifier": "importing-target-project_0.0.2-target-mock__61665553",
|
||||
"targetProject": {
|
||||
"mainEntry": "./target-src/match-imports/root-level-imports.js",
|
||||
"name": "importing-target-project",
|
||||
"version": "0.0.2-target-mock",
|
||||
"commitHash": "[not-a-git-root]"
|
||||
},
|
||||
"configuration": {
|
||||
"gatherFilesConfig": {},
|
||||
"skipCheckMatchCompatibility": false,
|
||||
"addSystemPathsInResult": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"queryOutput": [
|
||||
{
|
||||
"file": "./target-src/find-customelements/multiple.js",
|
||||
"result": [
|
||||
{
|
||||
"tagName": "ref-class",
|
||||
"constructorIdentifier": "RefClass",
|
||||
"rootFile": {
|
||||
"file": "exporting-ref-project",
|
||||
"specifier": "RefClass"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tagName": "extended-comp",
|
||||
"constructorIdentifier": "ExtendedComp",
|
||||
"rootFile": {
|
||||
"file": "./target-src/match-subclasses/ExtendedComp.js",
|
||||
"specifier": "ExtendedComp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tagName": "on-the-fly",
|
||||
"constructorIdentifier": "[inline]",
|
||||
"rootFile": {
|
||||
"file": "[current]",
|
||||
"specifier": "[inline]"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
{
|
||||
"meta": {
|
||||
"searchType": "ast-analyzer",
|
||||
"analyzerMeta": {
|
||||
"name": "find-exports",
|
||||
"requiredAst": "swc-to-babel",
|
||||
"identifier": "exporting-ref-project_1.0.0__-42206859",
|
||||
"targetProject": {
|
||||
"mainEntry": "./index.js",
|
||||
"name": "exporting-ref-project",
|
||||
"version": "1.0.0",
|
||||
"commitHash": "[not-a-git-root]"
|
||||
},
|
||||
"configuration": {
|
||||
"skipFileImports": false,
|
||||
"gatherFilesConfig": {},
|
||||
"skipCheckMatchCompatibility": false,
|
||||
"addSystemPathsInResult": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"queryOutput": [
|
||||
{
|
||||
"file": "./index.js",
|
||||
"result": [
|
||||
{
|
||||
"exportSpecifiers": [
|
||||
"[default]"
|
||||
],
|
||||
"source": "./ref-src/core.js",
|
||||
"normalizedSource": "./ref-src/core.js",
|
||||
"rootFileMap": [
|
||||
{
|
||||
"currentFileSpecifier": "[default]",
|
||||
"rootFile": {
|
||||
"file": "./ref-src/core.js",
|
||||
"specifier": "[default]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"exportSpecifiers": [
|
||||
"RefClass",
|
||||
"RefRenamedClass"
|
||||
],
|
||||
"localMap": [
|
||||
{
|
||||
"local": "RefClass",
|
||||
"exported": "RefRenamedClass"
|
||||
}
|
||||
],
|
||||
"source": "./ref-src/core.js",
|
||||
"normalizedSource": "./ref-src/core.js",
|
||||
"rootFileMap": [
|
||||
{
|
||||
"currentFileSpecifier": "RefClass",
|
||||
"rootFile": {
|
||||
"file": "./ref-src/core.js",
|
||||
"specifier": "RefClass"
|
||||
}
|
||||
},
|
||||
{
|
||||
"currentFileSpecifier": "RefRenamedClass",
|
||||
"rootFile": {
|
||||
"file": "./ref-src/core.js",
|
||||
"specifier": "RefClass"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"exportSpecifiers": [
|
||||
"[file]"
|
||||
],
|
||||
"rootFileMap": [
|
||||
null
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "./not-imported.js",
|
||||
"result": [
|
||||
{
|
||||
"exportSpecifiers": [
|
||||
"notImported"
|
||||
],
|
||||
"localMap": [],
|
||||
"rootFileMap": [
|
||||
{
|
||||
"currentFileSpecifier": "notImported",
|
||||
"rootFile": {
|
||||
"file": "[current]",
|
||||
"specifier": "notImported"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"exportSpecifiers": [
|
||||
"[file]"
|
||||
],
|
||||
"rootFileMap": [
|
||||
null
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "./ref-component.js",
|
||||
"result": [
|
||||
{
|
||||
"exportSpecifiers": [
|
||||
"[file]"
|
||||
],
|
||||
"rootFileMap": [
|
||||
null
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "./ref-src/core.js",
|
||||
"result": [
|
||||
{
|
||||
"exportSpecifiers": [
|
||||
"RefClass"
|
||||
],
|
||||
"localMap": [],
|
||||
"rootFileMap": [
|
||||
{
|
||||
"currentFileSpecifier": "RefClass",
|
||||
"rootFile": {
|
||||
"file": "[current]",
|
||||
"specifier": "RefClass"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"exportSpecifiers": [
|
||||
"[default]"
|
||||
],
|
||||
"rootFileMap": [
|
||||
{
|
||||
"currentFileSpecifier": "[default]",
|
||||
"rootFile": {
|
||||
"file": "[current]",
|
||||
"specifier": "[default]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"exportSpecifiers": [
|
||||
"[file]"
|
||||
],
|
||||
"rootFileMap": [
|
||||
null
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "./ref-src/folder/index.js",
|
||||
"result": [
|
||||
{
|
||||
"exportSpecifiers": [
|
||||
"resolvePathCorrect"
|
||||
],
|
||||
"localMap": [],
|
||||
"rootFileMap": [
|
||||
{
|
||||
"currentFileSpecifier": "resolvePathCorrect",
|
||||
"rootFile": {
|
||||
"file": "[current]",
|
||||
"specifier": "resolvePathCorrect"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"exportSpecifiers": [
|
||||
"[file]"
|
||||
],
|
||||
"rootFileMap": [
|
||||
null
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
{
|
||||
"meta": {
|
||||
"searchType": "ast-analyzer",
|
||||
"analyzerMeta": {
|
||||
"name": "find-imports",
|
||||
"requiredAst": "swc-to-babel",
|
||||
"identifier": "importing-target-project_0.0.2-target-mock__349742630",
|
||||
"targetProject": {
|
||||
"mainEntry": "./target-src/match-imports/root-level-imports.js",
|
||||
"name": "importing-target-project",
|
||||
"version": "0.0.2-target-mock",
|
||||
"commitHash": "[not-a-git-root]"
|
||||
},
|
||||
"configuration": {
|
||||
"keepInternalSources": false,
|
||||
"gatherFilesConfig": {},
|
||||
"skipCheckMatchCompatibility": false,
|
||||
"addSystemPathsInResult": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"queryOutput": [
|
||||
{
|
||||
"file": "./target-src/find-customelements/multiple.js",
|
||||
"result": [
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"RefClass"
|
||||
],
|
||||
"source": "exporting-ref-project",
|
||||
"normalizedSource": "exporting-ref-project"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "./target-src/find-imports/all-notations.js",
|
||||
"result": [
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"[file]"
|
||||
],
|
||||
"source": "imported/source",
|
||||
"normalizedSource": "imported/source"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"[default]"
|
||||
],
|
||||
"source": "imported/source-a",
|
||||
"normalizedSource": "imported/source-a"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"b"
|
||||
],
|
||||
"source": "imported/source-b",
|
||||
"normalizedSource": "imported/source-b"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"c",
|
||||
"d"
|
||||
],
|
||||
"source": "imported/source-c",
|
||||
"normalizedSource": "imported/source-c"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"[default]",
|
||||
"f",
|
||||
"g"
|
||||
],
|
||||
"source": "imported/source-d",
|
||||
"normalizedSource": "imported/source-d"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"[default]"
|
||||
],
|
||||
"source": "my/source-e",
|
||||
"normalizedSource": "my/source-e"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"[default]"
|
||||
],
|
||||
"source": "[variable]",
|
||||
"normalizedSource": "[variable]"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"[*]"
|
||||
],
|
||||
"source": "imported/source-g",
|
||||
"normalizedSource": "imported/source-g"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "./target-src/match-imports/deep-imports.js",
|
||||
"result": [
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"RefClass"
|
||||
],
|
||||
"source": "exporting-ref-project/ref-src/core.js",
|
||||
"normalizedSource": "exporting-ref-project/ref-src/core.js"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"[default]"
|
||||
],
|
||||
"source": "exporting-ref-project/ref-src/core.js",
|
||||
"normalizedSource": "exporting-ref-project/ref-src/core.js"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"nonMatched"
|
||||
],
|
||||
"source": "unknown-project/xyz.js",
|
||||
"normalizedSource": "unknown-project/xyz.js"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"[file]"
|
||||
],
|
||||
"source": "exporting-ref-project/ref-component",
|
||||
"normalizedSource": "exporting-ref-project/ref-component"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"resolvePathCorrect"
|
||||
],
|
||||
"source": "exporting-ref-project/ref-src/folder",
|
||||
"normalizedSource": "exporting-ref-project/ref-src/folder"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"[*]"
|
||||
],
|
||||
"source": "exporting-ref-project/ref-src/core.js",
|
||||
"normalizedSource": "exporting-ref-project/ref-src/core.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "./target-src/match-imports/root-level-imports.js",
|
||||
"result": [
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"RefClass"
|
||||
],
|
||||
"source": "exporting-ref-project",
|
||||
"normalizedSource": "exporting-ref-project"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"RefRenamedClass"
|
||||
],
|
||||
"source": "exporting-ref-project",
|
||||
"normalizedSource": "exporting-ref-project"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"[default]"
|
||||
],
|
||||
"source": "exporting-ref-project",
|
||||
"normalizedSource": "exporting-ref-project"
|
||||
},
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"nonMatched"
|
||||
],
|
||||
"source": "unknown-project",
|
||||
"normalizedSource": "unknown-project"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "./target-src/match-subclasses/ExtendedComp.js",
|
||||
"result": [
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"RefClass"
|
||||
],
|
||||
"source": "exporting-ref-project",
|
||||
"normalizedSource": "exporting-ref-project"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "./target-src/match-subclasses/internalProxy.js",
|
||||
"result": [
|
||||
{
|
||||
"importSpecifiers": [
|
||||
"[default]"
|
||||
],
|
||||
"source": "exporting-ref-project",
|
||||
"normalizedSource": "exporting-ref-project"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"meta": {
|
||||
"searchType": "ast-analyzer",
|
||||
"analyzerMeta": {
|
||||
"name": "match-imports",
|
||||
"requiredAst": "babel",
|
||||
"identifier": "importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209",
|
||||
"targetProject": {
|
||||
"mainEntry": "./target-src/match-imports/root-level-imports.js",
|
||||
"name": "importing-target-project",
|
||||
"version": "0.0.2-target-mock",
|
||||
"commitHash": "[not-a-git-root]"
|
||||
},
|
||||
"referenceProject": {
|
||||
"mainEntry": "./index.js",
|
||||
"name": "exporting-ref-project",
|
||||
"version": "1.0.0",
|
||||
"commitHash": "[not-a-git-root]"
|
||||
},
|
||||
"configuration": {
|
||||
"gatherFilesConfig": {},
|
||||
"prefix": null,
|
||||
"skipCheckMatchCompatibility": false,
|
||||
"addSystemPathsInResult": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"queryOutput": [
|
||||
{
|
||||
"name": "[default]",
|
||||
"variable": {
|
||||
"from": "[default]",
|
||||
"to": "ExtendedComp",
|
||||
"paths": [
|
||||
{
|
||||
"from": "./index.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "./ref-src/core.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "exporting-ref-project/index.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "exporting-ref-project/ref-src/core.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "RefClass",
|
||||
"variable": {
|
||||
"from": "RefClass",
|
||||
"to": "ExtendedComp",
|
||||
"paths": [
|
||||
{
|
||||
"from": "./index.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "./ref-src/core.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "exporting-ref-project/index.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "exporting-ref-project/ref-src/core.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tag": {
|
||||
"from": "ref-component",
|
||||
"to": "extended-comp",
|
||||
"paths": [
|
||||
{
|
||||
"from": "./ref-component.js",
|
||||
"to": "./target-src/find-customelements/multiple.js"
|
||||
},
|
||||
{
|
||||
"from": "exporting-ref-project/ref-component.js",
|
||||
"to": "./target-src/find-customelements/multiple.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"meta": {
|
||||
"searchType": "ast-analyzer",
|
||||
"analyzerMeta": {
|
||||
"name": "match-paths",
|
||||
"requiredAst": "babel",
|
||||
"identifier": "importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__142861209",
|
||||
"targetProject": {
|
||||
"mainEntry": "./target-src/match-imports/root-level-imports.js",
|
||||
"name": "importing-target-project",
|
||||
"version": "0.0.2-target-mock",
|
||||
"commitHash": "[not-a-git-root]"
|
||||
},
|
||||
"referenceProject": {
|
||||
"mainEntry": "./index.js",
|
||||
"name": "exporting-ref-project",
|
||||
"version": "1.0.0",
|
||||
"commitHash": "[not-a-git-root]"
|
||||
},
|
||||
"configuration": {
|
||||
"gatherFilesConfig": {},
|
||||
"prefix": null,
|
||||
"skipCheckMatchCompatibility": false,
|
||||
"addSystemPathsInResult": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"queryOutput": [
|
||||
{
|
||||
"name": "[default]",
|
||||
"variable": {
|
||||
"from": "[default]",
|
||||
"to": "ExtendedComp",
|
||||
"paths": [
|
||||
{
|
||||
"from": "./index.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "./ref-src/core.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "exporting-ref-project/index.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "exporting-ref-project/ref-src/core.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "RefClass",
|
||||
"variable": {
|
||||
"from": "RefClass",
|
||||
"to": "ExtendedComp",
|
||||
"paths": [
|
||||
{
|
||||
"from": "./index.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "./ref-src/core.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "exporting-ref-project/index.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
},
|
||||
{
|
||||
"from": "exporting-ref-project/ref-src/core.js",
|
||||
"to": "./target-src/match-subclasses/ExtendedComp.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tag": {
|
||||
"from": "ref-component",
|
||||
"to": "extended-comp",
|
||||
"paths": [
|
||||
{
|
||||
"from": "./ref-component.js",
|
||||
"to": "./target-src/find-customelements/multiple.js"
|
||||
},
|
||||
{
|
||||
"from": "exporting-ref-project/ref-component.js",
|
||||
"to": "./target-src/find-customelements/multiple.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"meta": {
|
||||
"searchType": "ast-analyzer",
|
||||
"analyzerMeta": {
|
||||
"name": "match-subclasses",
|
||||
"requiredAst": "babel",
|
||||
"identifier": "importing-target-project_0.0.2-target-mock_+_exporting-ref-project_1.0.0__1982316146",
|
||||
"targetProject": {
|
||||
"mainEntry": "./target-src/match-imports/root-level-imports.js",
|
||||
"name": "importing-target-project",
|
||||
"version": "0.0.2-target-mock",
|
||||
"commitHash": "[not-a-git-root]"
|
||||
},
|
||||
"referenceProject": {
|
||||
"mainEntry": "./index.js",
|
||||
"name": "exporting-ref-project",
|
||||
"version": "1.0.0",
|
||||
"commitHash": "[not-a-git-root]"
|
||||
},
|
||||
"configuration": {
|
||||
"gatherFilesConfig": {},
|
||||
"skipCheckMatchCompatibility": false,
|
||||
"addSystemPathsInResult": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"queryOutput": [
|
||||
{
|
||||
"exportSpecifier": {
|
||||
"name": "[default]",
|
||||
"project": "exporting-ref-project",
|
||||
"filePath": "./index.js",
|
||||
"id": "[default]::./index.js::exporting-ref-project"
|
||||
},
|
||||
"matchesPerProject": [
|
||||
{
|
||||
"project": "importing-target-project",
|
||||
"files": [
|
||||
{
|
||||
"file": "./target-src/match-subclasses/ExtendedComp.js",
|
||||
"identifier": "ExtendedComp"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"exportSpecifier": {
|
||||
"name": "RefClass",
|
||||
"project": "exporting-ref-project",
|
||||
"filePath": "./index.js",
|
||||
"id": "RefClass::./index.js::exporting-ref-project"
|
||||
},
|
||||
"matchesPerProject": [
|
||||
{
|
||||
"project": "importing-target-project",
|
||||
"files": [
|
||||
{
|
||||
"file": "./target-src/match-subclasses/ExtendedComp.js",
|
||||
"identifier": "ExtendedComp"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,330 @@
|
|||
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.eql(['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.eql(['[default]']);
|
||||
expect(firstResult.source).to.be.undefined;
|
||||
});
|
||||
|
||||
it(`supports [export default fn(){}] (default 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.eql(['[default]']);
|
||||
expect(firstResult.source).to.equal(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.eql({
|
||||
exportSpecifiers: ['[default]'],
|
||||
source: undefined,
|
||||
rootFileMap: [
|
||||
{
|
||||
currentFileSpecifier: '[default]',
|
||||
rootFile: { file: '[current]', specifier: '[default]' },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const secondEntry = getEntry(queryResults[0], 1);
|
||||
expect(secondEntry.result[0]).to.eql({
|
||||
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.eql({
|
||||
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.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'`]);
|
||||
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.eql([
|
||||
{
|
||||
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.eql(['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.eql([
|
||||
{
|
||||
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.eql([
|
||||
{
|
||||
currentFileSpecifier: 'InBetweenComp',
|
||||
rootFile: {
|
||||
file: './src/OriginalComp.js',
|
||||
specifier: 'OriginalComp',
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(thirdEntry.result[0].rootFileMap).to.eql([
|
||||
{
|
||||
currentFileSpecifier: 'OriginalComp',
|
||||
rootFile: {
|
||||
file: '[current]',
|
||||
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.eql([
|
||||
{
|
||||
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.eql(['[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.eql(['fooCategory']);
|
||||
// not mutually exclusive...
|
||||
expect(secondEntry.meta.categories).to.eql(['barCategory', 'testCategory']);
|
||||
expect(thirdEntry.meta.categories).to.eql([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -21,7 +21,7 @@ describe('Analyzer "find-exports"', async () => {
|
|||
};
|
||||
|
||||
describe('Export notations', () => {
|
||||
it(`supports [export const x = 0] (named specifier)`, async () => {
|
||||
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];
|
||||
|
|
@ -30,7 +30,7 @@ describe('Analyzer "find-exports"', async () => {
|
|||
expect(firstResult.source).to.be.undefined;
|
||||
});
|
||||
|
||||
it(`supports [export default class X {}] (default export)`, async () => {
|
||||
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];
|
||||
|
|
@ -38,16 +38,16 @@ describe('Analyzer "find-exports"', async () => {
|
|||
expect(firstResult.source).to.be.undefined;
|
||||
});
|
||||
|
||||
it(`supports [export default fn(){}] (default export)`, async () => {
|
||||
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.eql(['[default]']);
|
||||
expect(firstResult.source).to.equal(undefined);
|
||||
expect(firstResult.source).to.be.undefined;
|
||||
});
|
||||
|
||||
it(`supports [export {default as x} from 'y'] (default re-export)`, async () => {
|
||||
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':
|
||||
|
|
@ -82,7 +82,7 @@ describe('Analyzer "find-exports"', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
it(`supports [import {x} from 'y'; export default x] (named re-export as default)`, async () => {
|
||||
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]);
|
||||
|
|
@ -91,7 +91,7 @@ describe('Analyzer "find-exports"', async () => {
|
|||
expect(firstEntry.result[0].source).to.equal('y');
|
||||
});
|
||||
|
||||
it(`supports [import x from 'y'; export default x] (default re-export as default)`, async () => {
|
||||
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]);
|
||||
|
|
@ -100,7 +100,7 @@ describe('Analyzer "find-exports"', async () => {
|
|||
expect(firstEntry.result[0].source).to.equal('y');
|
||||
});
|
||||
|
||||
it(`supports [export { x } from 'my/source'] (re-export named specifier)`, async () => {
|
||||
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]);
|
||||
|
|
@ -169,7 +169,7 @@ describe('Analyzer "find-exports"', async () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it(`supports [export { x, y } from 'my/source'] (multiple re-exported named specifiers)`, async () => {
|
||||
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]);
|
||||
|
|
@ -185,6 +185,7 @@ describe('Analyzer "find-exports"', async () => {
|
|||
'./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);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
import { expect } from 'chai';
|
||||
import { it } from 'mocha';
|
||||
import babelTraverse from '@babel/traverse';
|
||||
import { swcTraverse } from '../../../../src/program/utils/swc-traverse.js';
|
||||
import {
|
||||
trackDownIdentifier,
|
||||
trackDownIdentifierFromScope,
|
||||
} from '../../../../src/program/analyzers/helpers/track-down-identifier.js';
|
||||
import { AstService } from '../../../../src/program/core/AstService.js';
|
||||
import {
|
||||
mockProject,
|
||||
restoreMockedProjects,
|
||||
} from '../../../../test-helpers/mock-project-helpers.js';
|
||||
import { mockProject } from '../../../../test-helpers/mock-project-helpers.js';
|
||||
import { setupAnalyzerTest } from '../../../../test-helpers/setup-analyzer-test.js';
|
||||
|
||||
/**
|
||||
|
|
@ -285,9 +282,6 @@ describe('trackdownIdentifier', () => {
|
|||
specifier: 'IngAccordionInvokerButton',
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: improve perf
|
||||
describe.skip('Caching', () => {});
|
||||
});
|
||||
|
||||
describe('trackDownIdentifierFromScope', () => {
|
||||
|
|
@ -299,7 +293,8 @@ describe('trackDownIdentifierFromScope', () => {
|
|||
};
|
||||
|
||||
mockProject(projectFiles, { projectName: 'my-project', projectPath: '/my/project' });
|
||||
const ast = AstService._getBabelAst(projectFiles['./src/declarationOfMyClass.js']);
|
||||
// const ast = AstService._getBabelAst(projectFiles['./src/declarationOfMyClass.js']);
|
||||
const ast = AstService._getSwcAst(projectFiles['./src/declarationOfMyClass.js']);
|
||||
|
||||
// Let's say we want to track down 'MyClass' in the code above
|
||||
const identifierNameInScope = 'MyClass';
|
||||
|
|
@ -308,7 +303,12 @@ describe('trackDownIdentifierFromScope', () => {
|
|||
/** @type {NodePath} */
|
||||
let astPath;
|
||||
|
||||
babelTraverse.default(ast, {
|
||||
// babelTraverse.default(ast, {
|
||||
// ClassDeclaration(path) {
|
||||
// astPath = path;
|
||||
// },
|
||||
// });
|
||||
swcTraverse(ast, {
|
||||
ClassDeclaration(path) {
|
||||
astPath = path;
|
||||
},
|
||||
|
|
@ -344,7 +344,8 @@ describe('trackDownIdentifierFromScope', () => {
|
|||
};
|
||||
|
||||
mockProject(projectFiles, { projectName: 'my-project', projectPath: '/my/project' });
|
||||
const ast = AstService._getBabelAst(projectFiles['./imported.js']);
|
||||
// const ast = AstService._getBabelAst(projectFiles['./imported.js']);
|
||||
const ast = AstService._getSwcAst(projectFiles['./imported.js']);
|
||||
|
||||
// Let's say we want to track down 'MyClass' in the code above
|
||||
const identifierNameInScope = 'MyClass';
|
||||
|
|
@ -353,7 +354,12 @@ describe('trackDownIdentifierFromScope', () => {
|
|||
/** @type {NodePath} */
|
||||
let astPath;
|
||||
|
||||
babelTraverse.default(ast, {
|
||||
// babelTraverse.default(ast, {
|
||||
// ImportDeclaration(path) {
|
||||
// astPath = path;
|
||||
// },
|
||||
// });
|
||||
swcTraverse(ast, {
|
||||
ImportDeclaration(path) {
|
||||
astPath = path;
|
||||
},
|
||||
|
|
@ -372,7 +378,7 @@ describe('trackDownIdentifierFromScope', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it(`tracks down extended classes from a reexport`, async () => {
|
||||
it(`tracks down extended classes from a re-export`, async () => {
|
||||
const projectFiles = {
|
||||
'./src/classes.js': `
|
||||
export class El1 extends HTMLElement {}
|
||||
|
|
@ -386,7 +392,8 @@ describe('trackDownIdentifierFromScope', () => {
|
|||
};
|
||||
|
||||
mockProject(projectFiles, { projectName: 'my-project', projectPath: '/my/project' });
|
||||
const ast = AstService._getBabelAst(projectFiles['./imported.js']);
|
||||
// const ast = AstService._getBabelAst(projectFiles['./imported.js']);
|
||||
const ast = AstService._getSwcAst(projectFiles['./imported.js']);
|
||||
|
||||
// Let's say we want to track down 'MyClass' in the code above
|
||||
const identifierNameInScope = 'El1';
|
||||
|
|
@ -395,7 +402,12 @@ describe('trackDownIdentifierFromScope', () => {
|
|||
/** @type {NodePath} */
|
||||
let astPath;
|
||||
|
||||
babelTraverse.default(ast, {
|
||||
// babelTraverse.default(ast, {
|
||||
// ClassDeclaration(path) {
|
||||
// astPath = path;
|
||||
// },
|
||||
// });
|
||||
swcTraverse(ast, {
|
||||
ClassDeclaration(path) {
|
||||
astPath = path;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ describe('Analyzer', async () => {
|
|||
});
|
||||
|
||||
it('has a "requiredAst" string', async () => {
|
||||
expect(typeof dummyAnalyzer.requiredAst).to.equal('string');
|
||||
expect(typeof dummyAnalyzer.constructor.requiredAst).to.equal('string');
|
||||
const allowedAsts = ['babel'];
|
||||
expect(allowedAsts).to.include(dummyAnalyzer.requiredAst);
|
||||
expect(allowedAsts).to.include(dummyAnalyzer.constructor.requiredAst);
|
||||
});
|
||||
|
||||
it('has a "requiresReference" boolean', async () => {
|
||||
|
|
|
|||
|
|
@ -88,8 +88,7 @@ describe('getSourceCodeFragmentOfDeclaration', () => {
|
|||
it('handles class declarations', async () => {
|
||||
const fakeFs = {
|
||||
'/my/proj/exports/ajax.js': `
|
||||
import { AjaxClass as LionAjaxClass } from '../_legacy/ajax/index.js';
|
||||
|
||||
import { AjaxClass as LionAjaxClass } from 'some-external-package';
|
||||
export class AjaxClass extends LionAjaxClass {}
|
||||
`,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,454 @@
|
|||
import { expect } from 'chai';
|
||||
import { it } from 'mocha';
|
||||
// @ts-ignore
|
||||
import babelTraversePkg from '@babel/traverse';
|
||||
import { swcTraverse } from '../../../src/program/utils/swc-traverse.js';
|
||||
import { AstService } from '../../../src/program/core/AstService.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@swc/core').Module} SwcAstModule
|
||||
* @typedef {import('../../../types/index.js').SwcPath} SwcPath
|
||||
* @typedef {import('../../../types/index.js').SwcScope} SwcScope
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {SwcAstModule} swcAst
|
||||
*/
|
||||
function gatherAllScopes(swcAst) {
|
||||
/** @type {SwcScope[]} */
|
||||
const swcScopes = [];
|
||||
swcTraverse(swcAst, {
|
||||
enter({ scope }) {
|
||||
if (!swcScopes.includes(scope)) {
|
||||
swcScopes.push(scope);
|
||||
}
|
||||
},
|
||||
});
|
||||
return swcScopes;
|
||||
}
|
||||
|
||||
describe('swcTraverse', () => {
|
||||
describe('Visitor', () => {
|
||||
it('traverses an swc AST based on <Node.type> visitor', async () => {
|
||||
const code = `import x from 'y';`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
let foundImportDeclarationPath;
|
||||
const visitor = {
|
||||
ImportDeclaration(/** @type {SwcPath} */ path) {
|
||||
foundImportDeclarationPath = path;
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor);
|
||||
|
||||
expect(foundImportDeclarationPath).to.not.be.undefined;
|
||||
});
|
||||
|
||||
it('supports "enter" as a generic arrival handler', async () => {
|
||||
const code = `import x from 'y';`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
/** @type {string[]} */
|
||||
const foundTypes = [];
|
||||
const visitor = {
|
||||
/**
|
||||
* @param {any} path
|
||||
*/
|
||||
enter(path) {
|
||||
foundTypes.push(path.node.type);
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor);
|
||||
|
||||
expect(foundTypes).to.eql([
|
||||
'Module',
|
||||
'ImportDeclaration',
|
||||
'ImportDefaultSpecifier',
|
||||
'Identifier',
|
||||
'StringLiteral',
|
||||
]);
|
||||
});
|
||||
|
||||
it('supports "enter" and "exit" as generic handlers inside <Node.type> handlers', async () => {
|
||||
const code = `import x from 'y';`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
/** @type {string[]} */
|
||||
const visitedPaths = [];
|
||||
const visitor = {
|
||||
/**
|
||||
* @param {any} path
|
||||
*/
|
||||
ImportDeclaration: {
|
||||
enter(path) {
|
||||
visitedPaths.push({ path, phase: 'enter' });
|
||||
},
|
||||
exit(path) {
|
||||
visitedPaths.push({ path, phase: 'exit' });
|
||||
},
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor);
|
||||
|
||||
expect(visitedPaths[0].path).to.equal(visitedPaths[1].path);
|
||||
expect(visitedPaths[0].phase).to.equal('enter');
|
||||
expect(visitedPaths[1].phase).to.equal('exit');
|
||||
});
|
||||
|
||||
it('supports "root" as alternative for Program', async () => {
|
||||
const code = `import x from 'y';`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
let rootPath;
|
||||
const visitor = {
|
||||
/**
|
||||
* @param {any} path
|
||||
*/
|
||||
root(path) {
|
||||
rootPath = path;
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor);
|
||||
|
||||
// TODO: also add case for Script
|
||||
expect(rootPath.node.type).to.equal('Module');
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('Paths', () => {
|
||||
it(`adds {
|
||||
node: SwcNode;
|
||||
parent: SwcNode;
|
||||
stop: function;
|
||||
scope: SwcScope;
|
||||
parentPath: SwcPath;
|
||||
}`, async () => {});
|
||||
|
||||
it('supports getPathFromNode', async () => {});
|
||||
});
|
||||
|
||||
describe('Scopes', () => {
|
||||
describe('Lexical scoping', () => {
|
||||
it('creates scopes for blocks', async () => {
|
||||
const code = `
|
||||
const globalScope = 0;
|
||||
{
|
||||
const middleScope = 1;
|
||||
{
|
||||
const deepestScope = 2;
|
||||
}
|
||||
}
|
||||
const alsoGlobalScope = 3;
|
||||
`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
/** @type {SwcPath[]} */
|
||||
const declaratorPaths = [];
|
||||
const visitor = {
|
||||
/**
|
||||
* @param {any} path
|
||||
*/
|
||||
VariableDeclarator(path) {
|
||||
declaratorPaths.push(path);
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true });
|
||||
|
||||
expect(declaratorPaths[0].scope.id).to.equal(0);
|
||||
expect(declaratorPaths[1].scope.id).to.equal(1);
|
||||
expect(declaratorPaths[2].scope.id).to.equal(2);
|
||||
|
||||
expect(declaratorPaths[0].node.id.value).to.equal('globalScope');
|
||||
expect(Object.keys(declaratorPaths[0].scope.bindings)).to.eql([
|
||||
'globalScope',
|
||||
'alsoGlobalScope',
|
||||
]);
|
||||
// 0 and 3 are the same scope
|
||||
expect(declaratorPaths[0].scope).to.equal(declaratorPaths[3].scope);
|
||||
// Scope bindings refer to Declarator nodes
|
||||
expect(declaratorPaths[0].scope.bindings.globalScope.identifier).to.equal(
|
||||
declaratorPaths[0].node,
|
||||
);
|
||||
expect(declaratorPaths[0].scope.bindings.alsoGlobalScope.identifier).to.equal(
|
||||
declaratorPaths[3].node,
|
||||
);
|
||||
|
||||
expect(Object.keys(declaratorPaths[1].scope.bindings)).to.eql(['middleScope']);
|
||||
expect(Object.keys(declaratorPaths[2].scope.bindings)).to.eql(['deepestScope']);
|
||||
});
|
||||
|
||||
it('creates scopes for nested FunctionDeclaration', async () => {
|
||||
const code = `
|
||||
function globalFn() {
|
||||
function middleScope() {
|
||||
function deepestScope() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
/** @type {SwcPath[]} */
|
||||
const declaratorPaths = [];
|
||||
const visitor = {
|
||||
FunctionDeclaration(/** @type {any} */ path) {
|
||||
declaratorPaths.push(path);
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true });
|
||||
const scopes = gatherAllScopes(swcAst);
|
||||
|
||||
expect(scopes[1].path?.node).to.equal(declaratorPaths[0].node);
|
||||
expect(scopes[2].path?.node).to.equal(declaratorPaths[1].node);
|
||||
expect(scopes[3].path?.node).to.equal(declaratorPaths[2].node);
|
||||
});
|
||||
|
||||
it('creates scopes for ClassDeclaration', async () => {
|
||||
const code = `
|
||||
class X extends HTMLElement {
|
||||
constructor() {
|
||||
var x = 1;
|
||||
}
|
||||
|
||||
method() {
|
||||
window.alert('hi');
|
||||
}
|
||||
}
|
||||
`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
/** @type {SwcPath[]} */
|
||||
const declaratorPaths = [];
|
||||
const visitor = {
|
||||
VariableDeclarator(/** @type {any} */ path) {
|
||||
declaratorPaths.push(path);
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true });
|
||||
|
||||
expect(declaratorPaths[0].scope.id).to.equal(2);
|
||||
});
|
||||
|
||||
it('creates scopes SwitchStatement', async () => {
|
||||
const code = `
|
||||
const myCases = { a: true };
|
||||
switch (myCases) {
|
||||
case myCases.a:
|
||||
const x = 1;
|
||||
break;
|
||||
default:
|
||||
}`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
/** @type {SwcPath[]} */
|
||||
const declaratorPaths = [];
|
||||
const visitor = {
|
||||
VariableDeclarator(/** @type {any} */ path) {
|
||||
declaratorPaths.push(path);
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true });
|
||||
|
||||
expect(declaratorPaths[0].node.id.value).to.equal('myCases');
|
||||
expect(declaratorPaths[1].node.id.value).to.equal('x');
|
||||
expect(declaratorPaths[0].scope.id).to.equal(0);
|
||||
expect(declaratorPaths[1].scope.id).to.equal(1);
|
||||
});
|
||||
|
||||
it('creates scopes for ObjectExpression', async () => {
|
||||
const code = `
|
||||
export default {
|
||||
toString(dateObj, opt = {}) {},
|
||||
};
|
||||
`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
/** @type {SwcPath[]} */
|
||||
const results = [];
|
||||
const visitor = {
|
||||
MethodProperty(/** @type {any} */ path) {
|
||||
results.push(path);
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true });
|
||||
|
||||
expect(results[0].node.key.value).to.equal('toString');
|
||||
expect(results[0].scope.id).to.equal(0);
|
||||
});
|
||||
|
||||
it('works for KeyValueProperty', async () => {
|
||||
const code = `
|
||||
export const x = {
|
||||
y:() => {
|
||||
const z = 1;
|
||||
},
|
||||
};
|
||||
`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
/** @type {SwcPath[]} */
|
||||
const declaratorPaths = [];
|
||||
const visitor = {
|
||||
VariableDeclarator(/** @type {any} */ path) {
|
||||
declaratorPaths.push(path);
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true });
|
||||
|
||||
expect(declaratorPaths[0].node.id.value).to.equal('x');
|
||||
expect(declaratorPaths[1].node.id.value).to.equal('z');
|
||||
expect(declaratorPaths[0].scope.id).to.equal(0);
|
||||
expect(declaratorPaths[1].scope.id).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bindings', () => {
|
||||
it('binds const and lets to block scope', async () => {
|
||||
const code = `
|
||||
const globalScope = 0;
|
||||
{
|
||||
let middleScope = 1;
|
||||
{
|
||||
const deepestScope = 2;
|
||||
}
|
||||
}
|
||||
let alsoGlobalScope = 3;
|
||||
`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
/** @type {SwcPath[]} */
|
||||
const declaratorPaths = [];
|
||||
const visitor = {
|
||||
VariableDeclarator(/** @type {SwcPath} */ path) {
|
||||
declaratorPaths.push(path);
|
||||
},
|
||||
FunctionDeclaration(/** @type {SwcPath} */ path) {
|
||||
declaratorPaths.push(path);
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true });
|
||||
|
||||
expect(Object.keys(declaratorPaths[0].scope.bindings)).to.eql([
|
||||
'globalScope',
|
||||
'alsoGlobalScope',
|
||||
]);
|
||||
// Scope bindings refer to Declarator nodes
|
||||
expect(declaratorPaths[0].scope.bindings.globalScope.identifier).to.equal(
|
||||
declaratorPaths[0].node,
|
||||
);
|
||||
expect(declaratorPaths[0].scope.bindings.alsoGlobalScope.identifier).to.equal(
|
||||
declaratorPaths[3].node,
|
||||
);
|
||||
});
|
||||
|
||||
it('binds vars to function scope', async () => {
|
||||
const code = `
|
||||
var globalScope = 0;
|
||||
{
|
||||
var stillGlobalScope = 1;
|
||||
function middleScope() {
|
||||
var insideFnScope = 2;
|
||||
}
|
||||
}
|
||||
`;
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
|
||||
/** @type {SwcPath[]} */
|
||||
const declaratorPaths = [];
|
||||
const visitor = {
|
||||
VariableDeclarator(/** @type {SwcPath} */ path) {
|
||||
declaratorPaths.push(path);
|
||||
},
|
||||
};
|
||||
swcTraverse(swcAst, visitor, { needsAdvancedPaths: true });
|
||||
|
||||
expect(Object.keys(declaratorPaths[0].scope.bindings)).to.eql([
|
||||
'globalScope',
|
||||
'stillGlobalScope',
|
||||
]);
|
||||
expect(Object.keys(declaratorPaths[1].scope.bindings)).to.eql(['middleScope']);
|
||||
expect(Object.keys(declaratorPaths[2].scope.bindings)).to.eql(['insideFnScope']);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('References', () => {});
|
||||
});
|
||||
|
||||
describe('Babel compatibility', () => {
|
||||
const babelTraverse = babelTraversePkg.default;
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
*/
|
||||
async function compareScopeResultsWithBabel(code) {
|
||||
const swcAst = await AstService._getSwcAst(code);
|
||||
const babelAst = await AstService._getBabelAst(code);
|
||||
|
||||
/**
|
||||
* @type {any[]}
|
||||
*/
|
||||
const babelScopes = [];
|
||||
babelTraverse(babelAst, {
|
||||
// @ts-ignore
|
||||
enter({ scope }) {
|
||||
if (!babelScopes.includes(scope)) {
|
||||
babelScopes.push(scope);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/** @type {SwcScope[]} */
|
||||
const swcScopes = [];
|
||||
swcTraverse(swcAst, {
|
||||
enter({ scope }) {
|
||||
if (!swcScopes.includes(scope)) {
|
||||
swcScopes.push(scope);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const babelRootScopeIdOffset = babelScopes[0].uid;
|
||||
|
||||
expect(babelScopes.length).to.equal(swcScopes.length);
|
||||
for (let i = 0; i < babelScopes.length; i += 1) {
|
||||
expect(babelScopes[i].uid - babelRootScopeIdOffset).to.equal(swcScopes[i].id);
|
||||
expect(Object.keys(babelScopes[i].bindings)).to.eql(Object.keys(swcScopes[i].bindings));
|
||||
// expect(babelScopes[i].references).to.eql(swcResults[i].references);
|
||||
}
|
||||
}
|
||||
|
||||
it('handles all kinds of lexical scopes and bindings in a similar way', async () => {
|
||||
const code = `
|
||||
const globalScope = 0;
|
||||
function fn() {
|
||||
let middleScope = 2;
|
||||
function fn() {
|
||||
var parentScope = 3;
|
||||
}
|
||||
}
|
||||
const alsoGlobalScope = 4;
|
||||
|
||||
{
|
||||
const myCases = { a: true };
|
||||
{
|
||||
switch (myCases) {
|
||||
case myCases.a:
|
||||
const x = 1;
|
||||
break;
|
||||
default:
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Q {
|
||||
constructor() {
|
||||
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
await compareScopeResultsWithBabel(code);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -4,6 +4,6 @@
|
|||
"outDir": "./dist-types",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["src", "dashboard", "types"],
|
||||
"include": ["src", "types"],
|
||||
"exclude": ["dist-types"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
PathFromSystemRoot,
|
||||
QueryType,
|
||||
QueryResult,
|
||||
RequiredAst,
|
||||
AnalyzerAst,
|
||||
ImportOrExportId,
|
||||
Project,
|
||||
GatherFilesConfig,
|
||||
|
|
@ -16,6 +16,8 @@ import {
|
|||
*/
|
||||
export type AnalyzerName = `${'find' | 'match'}-${string}` | '';
|
||||
|
||||
export type AnalyzerAst = 'babel' | 'swc-to-babel' | 'swc';
|
||||
|
||||
// TODO: make sure that data structures of JSON output (generated in ReportService)
|
||||
// and data structure generated in Analyzer.prototype._finalize match exactly (move logic from ReportSerivce to _finalize)
|
||||
// so that these type definitions can be used to generate a json schema: https://www.npmjs.com/package/typescript-json-schema
|
||||
|
|
@ -27,7 +29,7 @@ export interface Meta {
|
|||
|
||||
export interface AnalyzerMeta {
|
||||
name: AnalyzerName;
|
||||
requiredAst: RequiredAst;
|
||||
requiredAst: AnalyzerAst;
|
||||
/* a unique hash based on target, reference and configuration */
|
||||
identifier: ImportOrExportId;
|
||||
/* target project meta object */
|
||||
|
|
|
|||
|
|
@ -53,11 +53,6 @@ export type RootFile = {
|
|||
specifier: SpecifierName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Required ast for the analysis. Currently, only Babel is supported
|
||||
*/
|
||||
export type RequiredAst = 'babel';
|
||||
|
||||
/**
|
||||
* Name entry found in package.json
|
||||
*/
|
||||
|
|
@ -186,4 +181,9 @@ export type PackageJson = {
|
|||
dependencies?: { [dependency: string]: string };
|
||||
devDependencies?: { [dependency: string]: string };
|
||||
workspaces?: string[];
|
||||
main?: string;
|
||||
};
|
||||
|
||||
export type LernaJson = {
|
||||
packages: string[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export * from './core/index.js';
|
||||
export * from './analyzers/index.js';
|
||||
export * from './misc.js';
|
||||
export * from './utils/index.js';
|
||||
|
|
|
|||
31
packages-node/providence-analytics/types/utils/index.d.ts
vendored
Normal file
31
packages-node/providence-analytics/types/utils/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
export type SwcScope = {
|
||||
id: number;
|
||||
parentScope?: Scope;
|
||||
bindings: { [key: string]: Binding };
|
||||
path: SwcPath | null;
|
||||
_pendingRefsWithoutBinding: SwcNode[];
|
||||
_isIsolatedBlockStatement: boolean;
|
||||
};
|
||||
|
||||
/* Binding https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md#toc-bindings */
|
||||
export type SwcBinding = {
|
||||
identifier: SwcNode;
|
||||
// kind: string;
|
||||
refs: SwcNode[];
|
||||
path: SwcPath;
|
||||
};
|
||||
|
||||
export type SwcPath = {
|
||||
node: SwcNode;
|
||||
parent: SwcNode;
|
||||
stop: function;
|
||||
scope: SwcScope;
|
||||
parentPath: SwcPath;
|
||||
};
|
||||
|
||||
type SwcVisitorFn = (swcPath: SwcPath) => void;
|
||||
export type SwcVisitor = {
|
||||
[key: string]: SwcVisitorFn | { enter?: SwcVisitorFn; leave?: SwcVisitorFn };
|
||||
};
|
||||
|
||||
export type SwcTraversalContext = { visitOnExitFns: (() => void)[]; scopeId: number };
|
||||
Loading…
Reference in a new issue