Skip to content

Instantly share code, notes, and snippets.

@adrianbrowning
Created September 12, 2024 12:20
Show Gist options
  • Select an option

  • Save adrianbrowning/4bf5e031e39e69e3d3c668b690bfb7f4 to your computer and use it in GitHub Desktop.

Select an option

Save adrianbrowning/4bf5e031e39e69e3d3c668b690bfb7f4 to your computer and use it in GitHub Desktop.
Simple ESM JS file import analyser
import * as acorn from "acorn";
import fs from "node:fs";
import path from "node:path";
const file = '/path/to/js/file.js';
const allNodes = new Map<string, TreeNode>();
class TreeNode {
public path: string;
public parent = new Set<TreeNode>();
public children = new Set<TreeNode>();
constructor(parent: TreeNode|null, path: string) {
this.path = path;
if (parent) this.parent.add(parent);
parent?.children.add(this);
allNodes.set(path, this);
if(this.path === "") debugger;
}
static createNode(parent: TreeNode|null, path: string) {
const found = allNodes.get(path);
if (found) {
if (parent) {
found.parent.add(parent);
parent.children.add(found);
}
if(found.path === "") debugger;
return found;
}
return new TreeNode(parent, path);
}
}
const base = new TreeNode(null, file);
const seenImports = new Map<string, TreeNode>();
const toProcessImports:Array<[string, TreeNode]> = [[file, base]];
while (toProcessImports.length > 0) {
const [currentFile, parentNode] = toProcessImports.shift()!;
if(seenImports.has(currentFile)) {
continue;
}
seenImports.set(currentFile, parentNode);
processBody(getParsedAST(currentFile).body, currentFile, parentNode);
}
debugger;
function processBody(body: Array<acorn.Statement | acorn.ModuleDeclaration>, currentFile:string, treeNode:TreeNode) {
for (const node of body) {
if (node.type !== "ImportDeclaration") continue;
if (typeof node.source.value !== "string") continue;
const importName = node.source.value.startsWith(".")
? getCurrentFile(node.source.value, currentFile)
: node.source.value;
const childNode = TreeNode.createNode(treeNode, importName);
if (node.source.value.startsWith(".")) {
toProcessImports.push([importName, childNode]);
}
}
}
function getCurrentFile(file: string, base:string) {
return path.normalize(path.join(path.dirname(base || ""),file))
}
function getParsedAST(file: string) {
const code = fs.readFileSync(file, 'utf8');
return acorn.parse(code, {sourceType: "module", ecmaVersion: "latest",});
}
function printTree(node: TreeNode, prefix: string = "", isLast: boolean = true, depth: number = 0, paths = new Set<string>()) {
// Output the current node
const newPrefix = prefix + (isLast ? " " : "│ ");
if (paths.has(node.path)){
console.log(newPrefix+ "---- Recursion detected -----");
return;
}
paths.add(node.path);
console.log(`${prefix}${isLast ? "└── " : "├── "}${node.path}`);
if (depth > 10) {
console.log(newPrefix+ "---- Skipping -----");
return;
}
const childrenArray = Array.from(node.children);
// Recurse over each child
childrenArray.forEach((child, index) => {
printTree(child, newPrefix, index === childrenArray.length - 1, depth++, paths);
});
}
printTree(base);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment