UNPKG

@cyclonedx/cdxgen

Version:

Creates CycloneDX Software Bill-of-Materials (SBOM) from source or container image

199 lines (186 loc) 4.89 kB
const babelParser = require("@babel/parser"); const babelTraverse = require("@babel/traverse").default; const { join } = require("path"); const fs = require("fs"); const path = require("path"); const IGNORE_DIRS = [ "node_modules", "venv", "docs", "test", "tests", "e2e", "examples", "cypress", "site-packages", "typings", "api_docs", "dev_docs", "types", "mock", "mocks" ]; const IGNORE_FILE_PATTERN = new RegExp( "(conf|test|spec|mock|\\.d)\\.(js|ts|tsx)$", "i" ); const getAllFiles = (dir, extn, files, result, regex) => { files = files || fs.readdirSync(dir); result = result || []; regex = regex || new RegExp(`\\${extn}$`); for (let i = 0; i < files.length; i++) { if (IGNORE_FILE_PATTERN.test(files[i])) { continue; } let file = join(dir, files[i]); if (fs.statSync(file).isDirectory()) { // Ignore directories const dirName = path.basename(file); if ( dirName.startsWith(".") || IGNORE_DIRS.includes(dirName.toLowerCase()) ) { continue; } try { result = getAllFiles(file, extn, fs.readdirSync(file), result, regex); } catch (error) { continue; } } else { if (regex.test(file)) { result.push(file); } } } return result; }; const babelParserOptions = { sourceType: "unambiguous", allowImportExportEverywhere: true, allowAwaitOutsideFunction: true, allowReturnOutsideFunction: true, allowSuperOutsideMethod: true, errorRecovery: true, allowUndeclaredExports: true, attachComment: false, plugins: [ "optionalChaining", "classProperties", "decorators-legacy", "exportDefaultFrom", "doExpressions", "numericSeparator", "dynamicImport", "jsx", "typescript" ] }; /** * Filter only references to (t|jsx?) or (less|scss) files for now. * Opt to use our relative paths. */ const setFileRef = (allImports, file, pathway) => { // remove unexpected extension imports if (/\.(svg|png|jpg|d\.ts)/.test(pathway)) { return; } // replace relative imports with full path let module = pathway; if (/\.\//g.test(pathway) || /\.\.\//g.test(pathway)) { module = path.resolve(file, "..", pathway); } // initialise or increase reference count for file if (allImports.hasOwnProperty(module)) { allImports[module] = allImports[module] + 1; } else { allImports[module] = 1; } // Handle module package name // Eg: zone.js/dist/zone will be referred to as zone.js in package.json if (!path.isAbsolute(module) && module.includes("/")) { const modPkg = module.split("/")[0]; if (allImports.hasOwnProperty(modPkg)) { allImports[modPkg] = allImports[modPkg] + 1; } else { allImports[modPkg] = 1; } } }; /** * Check AST tree for any (j|tsx?) files and set a file * references for any import, require or dynamic import files. */ const parseFileASTTree = (file, allImports) => { const ast = babelParser.parse( fs.readFileSync(file, "utf-8"), babelParserOptions ); babelTraverse(ast, { // Used for all ES6 import statements ImportDeclaration: (path) => { if (path && path.node) { setFileRef(allImports, file, path.node.source.value); } }, // For require('') statements Identifier: (path) => { if ( path && path.node && path.node.name === "require" && path.parent.type === "CallExpression" ) { setFileRef(allImports, file, path.parent.arguments[0].value); } }, // Use for dynamic imports like routes.jsx CallExpression: (path) => { if (path && path.node && path.node.callee.type === "Import") { setFileRef(allImports, file, path.node.arguments[0].value); } }, // Use for export barrells ExportAllDeclaration: (path) => { setFileRef(allImports, file, path.node.source.value); }, ExportNamedDeclaration: (path) => { // ensure there is a path export if (path && path.node && path.node.source) { setFileRef(allImports, file, path.node.source.value); } } }); }; /** * Return paths to all (j|tsx?) files. */ const getAllSrcJSAndTSFiles = (src) => Promise.all([ getAllFiles(src, ".js"), getAllFiles(src, ".jsx"), getAllFiles(src, ".ts"), getAllFiles(src, ".tsx") ]); /** * Where Node CLI runs from. */ const findJSImports = async (src) => { const allImports = {}; const errFiles = []; try { const promiseMap = await getAllSrcJSAndTSFiles(src); const srcFiles = promiseMap.flatMap((d) => d); for (const file of srcFiles) { try { parseFileASTTree(file, allImports); } catch (err) { errFiles.push(file); } } return allImports; } catch (err) { return allImports; } }; exports.findJSImports = findJSImports;