feat(providence): traverseHtml improvement + node builtin support

This commit is contained in:
Thijs Louisse 2023-05-09 16:09:29 +02:00
parent 0315b33acd
commit f4448394b9
21 changed files with 290 additions and 101 deletions

View file

@ -29,32 +29,32 @@
"scripts": { "scripts": {
"dashboard": "node ./dashboard/server.js --run-server --serve-from-package-root", "dashboard": "node ./dashboard/server.js --run-server --serve-from-package-root",
"postinstall": "npx patch-package", "postinstall": "npx patch-package",
"match-lion-imports": "npm run providence -- analyze match-subclasses --search-target-collection @lion-targets --reference-collection @lion-references --measure-perf --add-system-paths", "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", "providence": "node --max-old-space-size=8192 ./src/cli/index.js",
"publish-docs": "node ../../packages-node/publish-docs/src/cli.js --github-url https://github.com/ing-bank/lion/ --git-root-dir ../../", "publish-docs": "node ../../packages-node/publish-docs/src/cli.js --github-url https://github.com/ing-bank/lion/ --git-root-dir ../../",
"prepublishOnly": "npm run publish-docs", "prepublishOnly": "npm run publish-docs",
"test:node": "npm run test:node:unit && npm run test:node:e2e", "test:node": "npm run test:node:unit && npm run test:node:e2e",
"test:node:e2e": "mocha './test-node/**/*.e2e.{j,mj}s' --timeout 60000", "test:node:e2e": "mocha './test-node/**/*.e2e.js' --timeout 60000",
"test:node:unit": "mocha './test-node/**/*.test.{j,mj}s'" "test:node:unit": "mocha './test-node/**/*.test.js'"
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.21.3", "@babel/core": "^7.21.4",
"@babel/parser": "^7.21.3", "@babel/parser": "^7.21.4",
"@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-syntax-export-default-from": "^7.18.6", "@babel/plugin-syntax-export-default-from": "^7.18.6",
"@babel/plugin-syntax-import-assertions": "^7.20.0", "@babel/plugin-syntax-import-assertions": "^7.20.0",
"@babel/register": "^7.21.0", "@babel/register": "^7.21.0",
"@babel/traverse": "^7.21.3", "@babel/traverse": "^7.21.4",
"@babel/types": "^7.21.3", "@babel/types": "^7.21.4",
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.2",
"@swc/core": "^1.3.42", "@swc/core": "^1.3.46",
"@web/dev-server": "^0.1.37", "@web/dev-server": "^0.1.38",
"anymatch": "^3.1.3", "anymatch": "^3.1.3",
"commander": "^2.20.3", "commander": "^2.20.3",
"glob": "^7.2.3", "glob": "^8.1.0",
"inquirer": "^9.1.5", "inquirer": "^9.1.5",
"is-negated-glob": "^1.0.0", "is-negated-glob": "^1.0.0",
"lit-element": "~2.4.0", "lit-element": "~3.3.1",
"parse5": "^7.1.2", "parse5": "^7.1.2",
"read-package-tree": "5.3.1", "read-package-tree": "5.3.1",
"semver": "^7.3.8", "semver": "^7.3.8",

View file

@ -34,6 +34,6 @@ export default {
// Usually the references are different from the targets. // Usually the references are different from the targets.
// In this demo file, we test @lion usage amongst itself // In this demo file, we test @lion usage amongst itself
// Select via " providence analyze --reference-collection 'exampleCollection' " // Select via " providence analyze --reference-collection 'exampleCollection' "
'@lion-references': ['../../packages/ui/'], '@lion-references': lionScopedPackagePaths,
}, },
}; };

View file

@ -1,17 +0,0 @@
#!/usr/bin/env bash
# See https://gist.github.com/myusuf3/7f645819ded92bda6677
if [ -z "$1" ]; then
echo "Please define 'path/to/submodule'";
exit;
fi
# Remove the submodule entry from .git/config
git submodule deinit -f $1
# Remove the submodule directory from the superproject's .git/modules directory
rm -rf .git/modules/$1
# Remove the entry in .gitmodules and remove the submodule directory located at path/to/submodule
git rm -rf $1

View file

@ -1,5 +1,5 @@
/* eslint-disable no-shadow, no-param-reassign */ /* eslint-disable no-shadow, no-param-reassign */
import pathLib from 'path'; import path from 'path';
import t from '@babel/types'; import t from '@babel/types';
import babelTraverse from '@babel/traverse'; import babelTraverse from '@babel/traverse';
import { Analyzer } from '../core/Analyzer.js'; import { Analyzer } from '../core/Analyzer.js';
@ -251,7 +251,7 @@ export default class FindClassesAnalyzer extends Analyzer {
/** @type {FindClassesAnalyzerOutput} */ /** @type {FindClassesAnalyzerOutput} */
const queryOutput = await this._traverse(async (ast, { relativePath }) => { const queryOutput = await this._traverse(async (ast, { relativePath }) => {
const projectPath = cfg.targetProjectPath; const projectPath = cfg.targetProjectPath;
const fullPath = pathLib.resolve(projectPath, relativePath); const fullPath = path.resolve(projectPath, relativePath);
const transformedEntry = await findMembersPerAstEntry(ast, fullPath, projectPath); const transformedEntry = await findMembersPerAstEntry(ast, fullPath, projectPath);
return { result: transformedEntry }; return { result: transformedEntry };
}); });

View file

@ -109,9 +109,9 @@ export default class FindCustomelementsAnalyzer extends Analyzer {
/** /**
* Prepare * Prepare
*/ */
const analyzerResult = this._prepare(cfg); const cachedAnalyzerResult = this._prepare(cfg);
if (analyzerResult) { if (cachedAnalyzerResult) {
return analyzerResult; return cachedAnalyzerResult;
} }
/** /**

View file

@ -170,13 +170,13 @@ function findExportsPerAstFile(babelAst, { skipFileImports }) {
} }
}, },
}, },
ExportNamedDeclaration(path) { ExportNamedDeclaration(astPath) {
const exportSpecifiers = getExportSpecifiers(path.node); const exportSpecifiers = getExportSpecifiers(astPath.node);
const localMap = getLocalNameSpecifiers(path.node); const localMap = getLocalNameSpecifiers(astPath.node);
const source = path.node.source?.value; const source = astPath.node.source?.value;
const entry = { exportSpecifiers, localMap, source, __tmp: { path } }; const entry = { exportSpecifiers, localMap, source, __tmp: { astPath } };
if (path.node.assertions?.length) { if (astPath.node.assertions?.length) {
entry.assertionType = path.node.assertions[0].value?.value; entry.assertionType = astPath.node.assertions[0].value?.value;
} }
transformedFile.push(entry); transformedFile.push(entry);
}, },
@ -194,7 +194,7 @@ function findExportsPerAstFile(babelAst, { skipFileImports }) {
source = importOrDeclPath.parentPath.node.source.value; source = importOrDeclPath.parentPath.node.source.value;
} }
} }
transformedFile.push({ exportSpecifiers, source, __tmp: { path: defaultExportPath } }); transformedFile.push({ exportSpecifiers, source, __tmp: { astPath: defaultExportPath } });
}, },
}); });
@ -238,9 +238,9 @@ export default class FindExportsAnalyzer extends Analyzer {
/** /**
* Prepare * Prepare
*/ */
const analyzerResult = this._prepare(cfg); const cachedAnalyzerResult = this._prepare(cfg);
if (analyzerResult) { if (cachedAnalyzerResult) {
return analyzerResult; return cachedAnalyzerResult;
} }
/** /**

View file

@ -25,7 +25,13 @@ import { toPosixPath } from '../../utils/to-posix-path.js';
*/ */
export async function fromImportToExportPerspective({ importee, importer, importeeProjectPath }) { export async function fromImportToExportPerspective({ importee, importer, importeeProjectPath }) {
if (isRelativeSourcePath(importee)) { if (isRelativeSourcePath(importee)) {
LogService.warn('[fromImportToExportPerspective] Please only provide external import paths'); LogService.warn(
`[fromImportToExportPerspective] Please only provide external import paths for ${{
importee,
importer,
importeeProjectPath,
}}`,
);
return null; return null;
} }

View file

@ -1,5 +1,5 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import pathLib from 'path'; import path from 'path';
import { isRelativeSourcePath } from '../../utils/relative-source-path.js'; import { isRelativeSourcePath } from '../../utils/relative-source-path.js';
import { resolveImportPath } from '../../utils/resolve-import-path.js'; import { resolveImportPath } from '../../utils/resolve-import-path.js';
import { toPosixPath } from '../../utils/to-posix-path.js'; import { toPosixPath } from '../../utils/to-posix-path.js';
@ -7,7 +7,7 @@ import { toPosixPath } from '../../utils/to-posix-path.js';
/** /**
* @typedef {import('../../../../types/index.js').PathRelative} PathRelative * @typedef {import('../../../../types/index.js').PathRelative} PathRelative
* @typedef {import('../../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot
* @typedef {import('../../../../types/index.js').QueryOutput} QueryOutput * @typedef {import('../../../../types/index.js').FindImportsAnalyzerEntry} FindImportsAnalyzerEntry
*/ */
/** /**
@ -16,9 +16,9 @@ import { toPosixPath } from '../../utils/to-posix-path.js';
* @returns {PathRelative} * @returns {PathRelative}
*/ */
function toLocalPath(currentDirPath, resolvedPath) { function toLocalPath(currentDirPath, resolvedPath) {
let relativeSourcePath = pathLib.relative(currentDirPath, resolvedPath); let relativeSourcePath = path.relative(currentDirPath, resolvedPath);
if (!relativeSourcePath.startsWith('.')) { if (!relativeSourcePath.startsWith('.')) {
// correction on top of pathLib.resolve, which resolves local paths like // correction on top of path.resolve, which resolves local paths like
// (from import perspective) external modules. // (from import perspective) external modules.
// so 'my-local-files.js' -> './my-local-files.js' // so 'my-local-files.js' -> './my-local-files.js'
relativeSourcePath = `./${relativeSourcePath}`; relativeSourcePath = `./${relativeSourcePath}`;
@ -28,36 +28,39 @@ function toLocalPath(currentDirPath, resolvedPath) {
/** /**
* Resolves and converts to normalized local/absolute path, based on file-system information. * Resolves and converts to normalized local/absolute path, based on file-system information.
* - from: { source: '../../relative/file' } * - from: '../../relative/file'
* - to: { * - to: './src/relative/file.js'
* fullPath: './absolute/path/from/root/to/relative/file.js', * @param {string} oldSource
* normalizedPath: '../../relative/file.js' * @param {string} relativePath
* } * @param {string} rootPath
* @param {QueryOutput} queryOutput */
export async function normalizeSourcePath(oldSource, relativePath, rootPath = process.cwd()) {
const currentFilePath = /** @type {PathFromSystemRoot} */ (path.resolve(rootPath, relativePath));
const currentDirPath = /** @type {PathFromSystemRoot} */ (path.dirname(currentFilePath));
if (isRelativeSourcePath(oldSource) && relativePath) {
// This will be a source like '../my/file.js' or './file.js'
const resolvedPath = /** @type {PathFromSystemRoot} */ (
await resolveImportPath(oldSource, currentFilePath)
);
return resolvedPath && toLocalPath(currentDirPath, resolvedPath);
}
// This will be a source from a project, like 'lion-based-ui/x.js' or '@open-wc/testing/y.js'
return oldSource;
}
/**
* @param {Partial<FindImportsAnalyzerEntry>[]} queryOutput
* @param {string} relativePath * @param {string} relativePath
* @param {string} rootPath * @param {string} rootPath
*/ */
export async function normalizeSourcePaths(queryOutput, relativePath, rootPath = process.cwd()) { export async function normalizeSourcePaths(queryOutput, relativePath, rootPath = process.cwd()) {
const currentFilePath = /** @type {PathFromSystemRoot} */ (
pathLib.resolve(rootPath, relativePath)
);
const currentDirPath = /** @type {PathFromSystemRoot} */ (pathLib.dirname(currentFilePath));
const normalizedQueryOutput = []; const normalizedQueryOutput = [];
for (const specifierResObj of queryOutput) { for (const specifierResObj of queryOutput) {
if (specifierResObj.source) { if (specifierResObj.source) {
if (isRelativeSourcePath(specifierResObj.source) && relativePath) { const x = await normalizeSourcePath(specifierResObj.source, relativePath, rootPath);
// This will be a source like '../my/file.js' or './file.js' if (x) {
const resolvedPath = /** @type {PathFromSystemRoot} */ ( specifierResObj.normalizedSource = x;
await resolveImportPath(specifierResObj.source, currentFilePath)
);
specifierResObj.normalizedSource =
resolvedPath && toLocalPath(currentDirPath, resolvedPath);
// specifierResObj.fullSource = resolvedPath && toRelativeSourcePath(resolvedPath, rootPath);
} else {
// This will be a source from a project, like 'lion-based-ui/x.js' or '@open-wc/testing/y.js'
specifierResObj.normalizedSource = specifierResObj.source;
// specifierResObj.fullSource = specifierResObj.source;
} }
} }
normalizedQueryOutput.push(specifierResObj); normalizedQueryOutput.push(specifierResObj);

View file

@ -1,5 +1,6 @@
/** /**
* @typedef {import('../../../../types/index.js').FindExportsAnalyzerResult} FindExportsAnalyzerResult * @typedef {import('../../../../types/index.js').FindExportsAnalyzerResult} FindExportsAnalyzerResult
* @typedef {import('../../../../types/index.js').IterableFindExportsAnalyzerEntry} IterableFindExportsAnalyzerEntry
*/ */
/** /**

View file

@ -1,5 +1,6 @@
/** /**
* @typedef {import('../../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult * @typedef {import('../../../../types/index.js').FindImportsAnalyzerResult} FindImportsAnalyzerResult
* @typedef {import('../../../../types/index.js').IterableFindImportsAnalyzerEntry} IterableFindImportsAnalyzerEntry
*/ */
/** /**
@ -43,13 +44,12 @@ export function transformIntoIterableFindImportsOutput(importsAnalyzerResult) {
continue; continue;
} }
for (const importSpecifier of importSpecifiers) { for (const importSpecifier of importSpecifiers) {
/** @type {IterableFindImportsAnalyzerEntry} */ const resultEntry = /** @type {IterableFindImportsAnalyzerEntry} */ ({
const resultEntry = {
file, file,
specifier: importSpecifier, specifier: importSpecifier,
source, source,
normalizedSource, normalizedSource,
}; });
iterableEntries.push(resultEntry); iterableEntries.push(resultEntry);
} }
} }

View file

@ -17,6 +17,8 @@ import { getFilePathRelativeFromRoot } from '../utils/get-file-path-relative-fro
* @typedef {import('../../../types/index.js').ProjectInputDataWithMeta} ProjectInputDataWithMeta * @typedef {import('../../../types/index.js').ProjectInputDataWithMeta} ProjectInputDataWithMeta
* @typedef {import('../../../types/index.js').AnalyzerQueryResult} AnalyzerQueryResult * @typedef {import('../../../types/index.js').AnalyzerQueryResult} AnalyzerQueryResult
* @typedef {import('../../../types/index.js').MatchAnalyzerConfig} MatchAnalyzerConfig * @typedef {import('../../../types/index.js').MatchAnalyzerConfig} MatchAnalyzerConfig
* @typedef {import('@babel/types').File} File
* @typedef {(ast: File, astContext: {code:string; relativePath:string; projectData: ProjectInputDataWithMeta}) => object} FileAstTraverseFn
*/ */
/** /**
@ -126,7 +128,7 @@ function ensureAnalyzerResultFormat(queryOutput, cfg, analyzer) {
* Before running the analyzer, we need two conditions for a 'compatible match': * Before running the analyzer, we need two conditions for a 'compatible match':
* - 1. referenceProject is imported by targetProject at all * - 1. referenceProject is imported by targetProject at all
* - 2. referenceProject and targetProject have compatible major versions * - 2. referenceProject and targetProject have compatible major versions
* @typedef {(referencePath:PathFromSystemRoot,targetPath:PathFromSystemRoot) => {compatible:boolean}} CheckForMatchCompatibilityFn * @typedef {(referencePath:PathFromSystemRoot,targetPath:PathFromSystemRoot) => {compatible:boolean; reason?:string}} CheckForMatchCompatibilityFn
* @type {CheckForMatchCompatibilityFn} * @type {CheckForMatchCompatibilityFn}
*/ */
const checkForMatchCompatibility = memoize( const checkForMatchCompatibility = memoize(
@ -172,7 +174,7 @@ export class Analyzer {
name = /** @type {typeof Analyzer} */ (this.constructor).analyzerName; name = /** @type {typeof Analyzer} */ (this.constructor).analyzerName;
/** @type {'babel'|'swc-to-babel'|'swc-to-babel'} */ /** @type {'babel'|'swc-to-babel'} */
requiredAst = 'babel'; requiredAst = 'babel';
/** /**
@ -183,10 +185,10 @@ export class Analyzer {
* @param {MatchAnalyzerConfig} cfg * @param {MatchAnalyzerConfig} cfg
*/ */
static __unwindProvidedResults(cfg) { static __unwindProvidedResults(cfg) {
if (cfg.targetProjectResult && !cfg.targetProjectResult.analyzerMeta) { if (cfg.targetProjectResult && !cfg.targetProjectResult?.analyzerMeta) {
cfg.targetProjectResult = unwindJsonResult(cfg.targetProjectResult); cfg.targetProjectResult = unwindJsonResult(cfg.targetProjectResult);
} }
if (cfg.referenceProjectResult && !cfg.referenceProjectResult.analyzerMeta) { if (cfg.referenceProjectResult && !cfg.referenceProjectResult?.analyzerMeta) {
cfg.referenceProjectResult = unwindJsonResult(cfg.referenceProjectResult); cfg.referenceProjectResult = unwindJsonResult(cfg.referenceProjectResult);
} }
} }
@ -296,7 +298,7 @@ export class Analyzer {
} }
/** /**
* @param {function|{traverseEntryFn: function; filePaths:string[]; projectPath: string}} traverseEntryOrConfig * @param {FileAstTraverseFn|{traverseEntryFn: FileAstTraverseFn; filePaths:string[]; projectPath: string}} traverseEntryOrConfig
*/ */
async _traverse(traverseEntryOrConfig) { async _traverse(traverseEntryOrConfig) {
LogService.debug(`Analyzer "${this.name}": started _traverse method`); LogService.debug(`Analyzer "${this.name}": started _traverse method`);

View file

@ -63,6 +63,17 @@ export function getReferencedDeclaration({ referencedIdentifierName, globalScope
} }
/** /**
* @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 * @param {{ filePath: PathFromSystemRoot; exportedIdentifier: string; projectRootPath: PathFromSystemRoot }} opts
* @returns {Promise<{ sourceNodePath: string; sourceFragment: string|null; externalImportSource: string; }>} * @returns {Promise<{ sourceNodePath: string; sourceFragment: string|null; externalImportSource: string; }>}
*/ */

View file

@ -1,4 +1,5 @@
import pathLib from 'path'; import { isBuiltin } from 'node:module';
import path from 'path';
import { nodeResolve } from '@rollup/plugin-node-resolve'; import { nodeResolve } from '@rollup/plugin-node-resolve';
import { LogService } from '../core/LogService.js'; import { LogService } from '../core/LogService.js';
import { memoize } from './memoize.js'; import { memoize } from './memoize.js';
@ -36,11 +37,15 @@ const fakePluginContext = {
* @param {SpecifierSource} importee source like '@lion/core' or '../helpers/index.js' * @param {SpecifierSource} importee source like '@lion/core' or '../helpers/index.js'
* @param {PathFromSystemRoot} importer importing file, like '/my/project/importing-file.js' * @param {PathFromSystemRoot} importer importing file, like '/my/project/importing-file.js'
* @param {{customResolveOptions?: {preserveSymlinks:boolean}}} [opts] nodeResolve options * @param {{customResolveOptions?: {preserveSymlinks:boolean}}} [opts] nodeResolve options
* @returns {Promise<PathFromSystemRoot|null>} the resolved file system path, like '/my/project/node_modules/@lion/core/index.js' * @returns {Promise<PathFromSystemRoot|null|'[node-builtin]'>} the resolved file system path, like '/my/project/node_modules/@lion/core/index.js'
*/ */
async function resolveImportPathFn(importee, importer, opts) { async function resolveImportPathFn(importee, importer, opts) {
if (isBuiltin(importee)) {
return '[node-builtin]';
}
const rollupResolve = nodeResolve({ const rollupResolve = nodeResolve({
rootDir: pathLib.dirname(importer), rootDir: path.dirname(importer),
// allow resolving polyfills for nodejs libs // allow resolving polyfills for nodejs libs
preferBuiltins: false, preferBuiltins: false,
// extensions: ['.mjs', '.js', '.json', '.node'], // extensions: ['.mjs', '.js', '.json', '.node'],
@ -48,23 +53,24 @@ async function resolveImportPathFn(importee, importer, opts) {
}); });
const preserveSymlinks = const preserveSymlinks =
(opts && opts.customResolveOptions && opts.customResolveOptions.preserveSymlinks) || false; (opts?.customResolveOptions && opts.customResolveOptions.preserveSymlinks) || false;
// @ts-ignore // @ts-expect-error
rollupResolve.buildStart.call(fakePluginContext, { preserveSymlinks }); rollupResolve.buildStart.call(fakePluginContext, { preserveSymlinks });
// @ts-ignore // @ts-expect-error
const result = await rollupResolve.resolveId.handler.call( const result = await rollupResolve.resolveId.handler.call(
fakePluginContext, fakePluginContext,
importee, importee,
importer, importer,
{}, {},
); );
// @ts-ignore
if (!result?.id) { if (!result?.id) {
LogService.warn(`importee ${importee} not found in filesystem for importer '${importer}'.`); LogService.warn(
`[resolveImportPath] importee ${importee} not found in filesystem for importer '${importer}'.`,
);
return null; return null;
} }
// @ts-ignore
return toPosixPath(result.id); return toPosixPath(result.id);
} }

View file

@ -1,25 +1,41 @@
/** /**
* @param {Node} curNode Node to start from. Will loop over its children * @typedef {import('parse5/dist/tree-adapters/default.js').Node} Node
*/
/**
* Creates an api similar to Babel traverse for parse5 trees
* @param {Parse5AstNode} curNode Node to start from. Will loop over its children
* @param {object} processObject Will be executed for every node * @param {object} processObject Will be executed for every node
*/ */
export function traverseHtml(curNode, processObject) { export function traverseHtml(curNode, processObject, config = {}) {
function pathify(node) { function pathify(node) {
return { return {
node, node,
traverse(obj) { traverseHtml(obj) {
traverseHtml(node, obj); traverseHtml(node, obj);
}, },
stop() {
// eslint-disable-next-line no-param-reassign
config.stopped = true;
},
}; };
} }
// let done = processFn(curNode, parentNode); // Match...
if (processObject[curNode.nodeName]) { if (processObject[curNode.nodeName]) {
processObject[curNode.nodeName](pathify(curNode)); processObject[curNode.nodeName](pathify(curNode));
} }
if (curNode.childNodes) { let { childNodes } = curNode;
curNode.childNodes.forEach(childNode => { if (curNode.nodeName === 'template') {
traverseHtml(childNode, processObject, curNode); childNodes = curNode.content.childNodes;
}
if (!config.stopped && childNodes) {
childNodes.forEach(childNode => {
if (!config.stopped) {
traverseHtml(childNode, processObject, config);
}
}); });
} }
} }

View file

@ -33,7 +33,6 @@ const options = {
*/ */
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
function getResultPerAstFile(ast) { function getResultPerAstFile(ast) {
console.debug('myAnalyzerPerAstEntry');
// Visit AST... // Visit AST...
const transformedEntryResult = []; const transformedEntryResult = [];
// Do the traverse: https://babeljs.io/docs/en/babel-traverse // Do the traverse: https://babeljs.io/docs/en/babel-traverse

View file

@ -9,7 +9,6 @@ import {
mockProject, mockProject,
mock, mock,
} from '../../../test-helpers/mock-project-helpers.js'; } from '../../../test-helpers/mock-project-helpers.js';
// import { setupAnalyzerTest } from '../../../test-helpers/setup-analyzer-test.js';
/** /**
* @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot * @typedef {import('../../../types/index.js').PathFromSystemRoot} PathFromSystemRoot

View file

@ -115,6 +115,25 @@ describe('resolveImportPath', () => {
expect(foundPath).to.equal('/target/node_modules/ref/packages/x/index.js'); expect(foundPath).to.equal('/target/node_modules/ref/packages/x/index.js');
}); });
it(`resolves native node modules`, async () => {
mockProject(
{
'./src/someFile.js': `
import { readFile } from 'fs';
export const x = readFile('/path/to/someOtherFile.js');
`,
},
{
projectName: 'my-project',
projectPath: '/my/project',
},
);
const foundPath = await resolveImportPath('fs', '/my/project/someFile.js');
expect(foundPath).to.equal('[node-builtin]');
});
/** /**
* All edge cases are covered by https://github.com/rollup/plugins/tree/master/packages/node-resolve/test * All edge cases are covered by https://github.com/rollup/plugins/tree/master/packages/node-resolve/test
*/ */

View file

@ -0,0 +1,140 @@
import { expect } from 'chai';
import { it } from 'mocha';
import * as parse5 from 'parse5';
import { traverseHtml } from '../../../src/program/utils/traverse-html.js';
function getId(p5Path) {
return p5Path.node.attrs.find(a => a.name === 'id').value;
}
describe('traverseHtml', () => {
it('finds different tag names', async () => {
const htmlCode = `
<div id="a-lvl1">
<span id="a-lvl2">
<my-tag id="a-lvl3">
<not-found></notfound>
</my-tag>
</span>
</div>
<div id="b"></div>
`;
const ast = parse5.parseFragment(htmlCode);
const foundDivs = [];
const foundSpans = [];
const foundMyTags = [];
traverseHtml(ast, {
div(p5Path) {
foundDivs.push(getId(p5Path));
},
span(p5Path) {
foundSpans.push(getId(p5Path));
},
// eslint-disable-next-line object-shorthand
'my-tag'(p5Path) {
foundMyTags.push(getId(p5Path));
},
});
expect(foundDivs).to.eql(['a-lvl1', 'b']);
expect(foundSpans).to.eql(['a-lvl2']);
expect(foundMyTags).to.eql(['a-lvl3']);
});
it('traverses different levels in DOM order', async () => {
const htmlCode = `
<div id="a-lvl1">
<span id="a-lvl2">
<my-tag id="a-lvl3">
<not-found></notfound>
</my-tag>
</span>
</div>
<div id="b"></div>
`;
const ast = parse5.parseFragment(htmlCode);
const callOrder = [];
const processObj = {
span(p5Path) {
callOrder.push(`span#${getId(p5Path)}`);
},
div(p5Path) {
callOrder.push(`div#${getId(p5Path)}`);
},
// eslint-disable-next-line object-shorthand
'my-tag'(p5Path) {
callOrder.push(`my-tag#${getId(p5Path)}`);
},
};
traverseHtml(ast, processObj);
// call order based on dom tree
expect(callOrder).to.eql(['div#a-lvl1', 'span#a-lvl2', 'my-tag#a-lvl3', 'div#b']);
});
it('allows to stop traversal (for performance)', async () => {
const htmlCode = `
<div id="a-lvl1">
<span id="a-lvl2">
<my-tag id="a-lvl3">
<not-found></notfound>
</my-tag>
</span>
</div>
<div id="b"></div>
`;
const ast = parse5.parseFragment(htmlCode);
const callOrder = [];
const processObj = {
div(p5Path) {
callOrder.push(`div#${getId(p5Path)}`);
p5Path.stop();
},
span(p5Path) {
callOrder.push(`span#${getId(p5Path)}`);
},
// eslint-disable-next-line object-shorthand
'my-tag'(p5Path) {
callOrder.push(`my-tag#${getId(p5Path)}`);
},
};
traverseHtml(ast, processObj);
expect(callOrder).to.eql(['div#a-lvl1']);
});
it('allows to traverse within a path', async () => {
const htmlCode = `
<div id="a-lvl1">
<span id="a-lvl2">
<my-tag id="a-lvl3">
<not-found id="a-lvl4"></notfound>
</my-tag>
</span>
</div>
<div id="b"></div>
`;
const ast = parse5.parseFragment(htmlCode);
const callOrder = [];
const processObj = {
// eslint-disable-next-line object-shorthand
'my-tag'(p5Path) {
callOrder.push(`my-tag#${getId(p5Path)}`);
p5Path.traverseHtml({
// eslint-disable-next-line object-shorthand, no-shadow
'not-found'(p5Path) {
callOrder.push(`not-found#${getId(p5Path)}`);
},
});
},
};
traverseHtml(ast, processObj);
expect(callOrder).to.eql(['my-tag#a-lvl3', 'not-found#a-lvl4']);
});
});

View file

@ -4,9 +4,11 @@ export type TargetDep = `${PkgName}#${PkgVersion}`;
export type TargetDepsObj = { export type TargetDepsObj = {
[key: TargetDep]: TargetDep[]; [key: TargetDep]: TargetDep[];
}; };
export type TargetOrRefCollectionsObj = { export type TargetOrRefCollectionsObj = {
[key: PkgName]: PkgName[]; [key: PkgName]: PkgName[];
}; };
export type ProvidenceCliConf = { export type ProvidenceCliConf = {
metaConfig: { metaConfig: {
categoryConfig: { categoryConfig: {
@ -35,3 +37,5 @@ export type ProvidenceCliConf = {
[referenceCollection: string]: string[]; [referenceCollection: string]: string[];
}; };
}; };
// export { Node as Parse5Node } from 'parse5/dist/tree-adapters/default/index.js';