RunKit + npm: flast

node v14.20.1
version: master
endpointsharetweet
const {generateFlatAST, generateCode} = require('flast'); const ast = generateFlatAST(`const { findReferences, constructString, constructTemplateLiteral } = require("./common"); const { getNodeModules } = require("./dependency-graph"); const { extractCalls } = require("./call-graph"); const { extractLibs } = require("./library-api"); const { basename, dirname, extname, relative, resolve } = require("path"); const { generateFlatAST } = require("flast"); const { promisify } = require("util"); const { readFile, stat } = require("fs/promises"); const glob = require("glob"); const randomColor = require("randomcolor"); /** * https://github.com/eslint/eslint/blob/b93af98b3c417225a027cabc964c38e779adb945/lib/linter/linter.js#L718 * Strips Unicode BOM from a given text. * @param {string} text A text to strip. * @returns {string} The stripped text. */ function stripUnicodeBOM(text) { /* * Check Unicode BOM. * In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF. * http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters */ if (text.charCodeAt(0) === 0xFEFF) { return text.slice(1); } return text; } async function scan(srcPath, dependencies, devDependencies, main) { let getFiles = promisify(glob); // all javascript files in the project (excluding node_modules) let pattern = resolve(\`\${srcPath}/{,!(node_modules)/**/}*.{js,mjs}\`).replace(/\\\\/g, "/"); let srcFiles = (await getFiles(pattern)).map((f) => resolve(f)); let hasNodeModules = await stat(resolve(srcPath, "./node_modules")).then(() => true) .catch(() => false); let node_modules = await getNodeModules(srcPath, hasNodeModules, dependencies, devDependencies); let externalLibs = Object.keys(dependencies || {}).concat(Object.keys(devDependencies || {})); let srcContents = {}; let libs = {}; let calls = {}; for (const file of srcFiles) { try { const code = await readFile(file); // https://github.com/eslint/eslint/blob/b93af98b3c417225a027cabc964c38e779adb945/lib/linter/linter.js#L779 const textToParse = stripUnicodeBOM(code.toString()).replace(/^#!([^\\r\\n]+)/u, (match, captured) => \`//\${captured}\`); const ast = generateFlatAST(textToParse, { "includeSrc": false, "parseOpts": { "ecmaVersion": "latest", "sourceType": "module" }, }); srcContents[file] = code.toString(); calls[file] = extractCalls(ast, file, srcPath); libs[file] = extractLibs(ast, file, srcPath); } catch (err) { console.error(\`Unable to parse "\${file}":\`, err); continue; } } if (global.debug) { console.log("FUNCTION CALLS\\n", calls); } // GROUPS let lunaData = [ { "data": { "label": "Source Code", "color": "#fff", "id": "files", "group": true } }, { "data": { "label": "Libraries", "color": "#fff", "id": "libs", "group": true } }, { "data": { "label": "Development", "color": "#fff", "parent": "libs", "id": "unused", "group": true } }, { "data": { "label": "Internal", "color": "#fff", "parent": "libs", "id": "internal", "group": true } }, { "data": { "label": "External", "color": "#fff", "parent": "libs", "id": "external", "group": true } }, { "data": { "label": "Dependency Tree", "color": "#fff", "id": "deps", "group": true } }, ]; // COLORS let libSet = Array.from(new Set(Object.values(libs).map((lib) => Object.keys(lib)) .flat() .concat(Object.keys(node_modules)))); if (global.debug) { console.log("LIBRARIES\\n", libSet); } let nLibs = libSet.length; let randomColors = randomColor({ "count": nLibs, "luminosity": "light", }); let colors = {}; // CALLS lunaData.push(...Object.values(calls).flat() .map((e) => Object.values(e)) .flat()); // FILES & LIBRARIES for (let file in libs) { let parentPath = dirname(file); let isRootFile = relative(srcPath, parentPath) === ""; let fileId = relative(srcPath, file); let fileName = basename(file); let fileExt = extname(file); // let parentFolder = basename(parentPath); let addParentGroup = (dir) => { let parentFolder = basename(dir); let parentParentFolder = dirname(dir); let isParentRoot = relative(srcPath, dirname(dir)) === ""; // group lunaData.push({ "data": { "label": \`/\${parentFolder}\`, "group": true, "color": "#fff", "parent": isParentRoot ? "files" : parentParentFolder, "id": dir, "isFolder": true, }, }); if (!isParentRoot) { addParentGroup(dirname(dir)); } }; if (!isRootFile) { addParentGroup(dirname(file)); } // node (file) lunaData.push({ "data": { "id": fileId, "isData": fileExt === ".json", "color": "#fff", "filePath": file, "size": { "loc": srcContents[file].split("\\n").length, "chars": srcContents[file].length, }, "parent": isRootFile ? "files" : parentPath, "label": fileName, "isMain": main ? resolve(file) === resolve(srcPath, main) : false, "group": true, }, }); for (let libName in libs[file]) { let id = libName; let isInternalDep = false; let version = ""; if (node_modules[libName]) { ({ version } = node_modules[libName]); id = \`\${libName}@\${version}\`; } else { let internalPath = resolve(dirname(file), libName); let ext = extname(internalPath); let altInternalPath = ext ? internalPath.replace(ext, "") : \`\${internalPath}.js\`; // extension may be included or omitted (assumption: .js) let indexInternalPath = ext ? null : resolve(internalPath, "index.js"); // require(x) <=> require(x/index.js) // If this internal dependency is among src files, it is a file node. let srcFile = srcFiles.find((f) => f === internalPath || f === altInternalPath || f === indexInternalPath); isInternalDep = Boolean(srcFile); if (isInternalDep) { let fileId = relative(srcPath, srcFile); id = fileId; } if (id.includes(srcPath)) { // correct path notation id = relative(srcPath, id); if (id === "") { id = "."; } } } if (!colors[id]) { colors[id] = randomColors.pop(); } if (!isInternalDep) { // node lunaData.push({ "data": { id, "isData": libName.toLowerCase().endsWith(".json"), // sufficient detection? "library": { "name": libName, version }, "color": colors[id], "parent": externalLibs.includes(libName) ? "external" : "internal", // group: external or internal "label": id, "group": true, }, }); } let recurseApi = (libName, api, source) => { for (let name in api) { if (source) { // node lunaData.push({ "data": { "parent": libName, "id": \`\${libName} | \${source}\`, "color": "#fff", "label": source, "type": "API", }, }); // edge lunaData.push({ "data": { "parent": libName, "id": \`\${libName} | \${source} -> \${name}\`, "color": "#fff", "source": \`\${libName} | \${source}\`, "target": \`\${libName} | \${name}\`, }, }); } // node lunaData.push({ "data": { "parent": libName, "id": \`\${libName} | \${name}\`, "color": "#fff", "label": name, "type": "API", }, }); // edge lunaData.push({ "data": { "id": \`\${libName} -> \${name}\`, "color": "#fff", // "source": fileId, "source": lunaData.find((e) => e.data.id === api[name].sourceId) ? api[name].sourceId : fileId, // Workaround, TODO: Fix this "target": \`\${libName} | \${name}\`, // TODO: swap source & target? "_sourceId": api[name].sourceId, }, }); recurseApi(libName, api[name].children, name); } }; if (Object.keys(libs[file][libName]).length > 0) { recurseApi(id, libs[file][libName]); } else { // edge lunaData.push({ "data": { "id": \`\${fileId} -> \${id}\`, "color": colors[id] || "#fff", "source": fileId, "target": id, }, }); } } } // DEPENDENCY TREE let traverseTree = (node, parent) => { for (let libName in node) { let lib = node[libName]; let { version } = lib; let id = \`\${libName}@\${version}\`; if (!colors[id]) { colors[id] = colors[parent] || randomColors.pop(); } if (!parent && !lunaData.find((n) => n.data.id === id)) { // if not already in tree // node lunaData.push({ "data": { id, "library": { "name": libName, version }, "color": colors[id], "parent": "unused", // group "label": id, "group": true, }, }); } if (parent) { // node lunaData.push({ "data": { id, "library": { "name": libName, version }, "color": colors[id], "parent": "deps", // group "label": id, }, }); // edge lunaData.push({ "data": { "id": \`\${parent} -> \${id}\`, "color": colors[id], "source": parent, "target": id, }, }); } if (lib.dependencies) { traverseTree(lib.dependencies, id); } } }; traverseTree(node_modules); if (global.debug) { console.log("DEPENDENCIES OF LIBRARIES (NODE_MODULES)\\n", node_modules); } return lunaData; } module.exports = { scan, constructString, constructTemplateLiteral }; exports.findReferences = findReferences; `); console.log(ast.find(e => e.callee && e.callee.name === "generateFlatAST"));
Created from: https://npm.runkit.com/flast
Loading…

no comments

    sign in to comment