dependency-cruiser
Version:
Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.
99 lines (88 loc) • 3.14 kB
JavaScript
import { readFileSync } from "node:fs";
import { Parser as acornParser, parse as acornParse } from "acorn";
import { parse as acornLooseParse } from "acorn-loose";
import acornJsx from "acorn-jsx";
import memoize from "lodash/memoize.js";
import transpile from "../transpile/index.mjs";
import getExtension from "../../utl/get-extension.mjs";
/** @type acorn.Options */
const ACORN_OPTIONS = {
sourceType: "module",
ecmaVersion: 11,
};
const TSCONFIG_CONSTANTS = {
PRESERVE_JSX: 1,
};
const acornJsxParser = acornParser.extend(acornJsx());
function needsJSXTreatment(pFileRecord, pTranspileOptions) {
return (
pFileRecord.extension === ".jsx" ||
(pFileRecord.extension === ".tsx" &&
pTranspileOptions?.tsConfig?.options?.jsx ===
TSCONFIG_CONSTANTS.PRESERVE_JSX)
);
}
export function getASTFromSource(pFileRecord, pTranspileOptions) {
const lJavaScriptSource = transpile(pFileRecord, pTranspileOptions);
try {
if (needsJSXTreatment(pFileRecord, pTranspileOptions)) {
return acornJsxParser.parse(lJavaScriptSource, {
...ACORN_OPTIONS,
// @ts-expect-error because ...
// acornJsxParser.parse takes an acorn.Options which doesn't include
// allowNamespacedObjects. acornJsx.Options doesn't include sourceType
// though, so a bit rock < this code > hard place hence ignore it
// type wise
allowNamespacedObjects: true,
});
}
// ecmaVersion 11 necessary for acorn to understand dynamic imports
// explicitly passing ecmaVersion is recommended from acorn 8
return acornParse(lJavaScriptSource, ACORN_OPTIONS);
} catch (pError) {
return acornLooseParse(lJavaScriptSource, ACORN_OPTIONS);
}
}
/**
* Returns the abstract syntax tree of the module identified by the passed
* file name. It obtains this by (1) transpiling the contents of the file
* into javascript (if necessary) (2) parsing it.
*
* If parsing fails we fall back to acorn's 'loose' parser
*
* @param {string} pFileName path to the file to be parsed
* @param {any} pTranspileOptions options for the transpiler(s) - a tsconfig or
* a babel config
* @returns {acorn.Node} the abstract syntax tree
*/
function getAST(pFileName, pTranspileOptions) {
return getASTFromSource(
{
source: readFileSync(pFileName, "utf8"),
extension: getExtension(pFileName),
filename: pFileName,
},
pTranspileOptions
);
}
/**
* Compiles the file identified by pFileName into a (javascript)
* AST and returns it. Subsequent calls for the same file name will
* return the result from a cache.
*
* @param {string} pFileName - the name of the file to compile
* @param {any} pTranspileOptions - options for the transpiler(s) - typically a tsconfig or a babel config
* @return {acorn.Node} - a (javascript) AST
*/
export const getASTCached = memoize(
getAST,
(pFileName, pTranspileOptions) => `${pFileName}|${pTranspileOptions}`
);
export function clearCache() {
getASTCached.cache.clear();
}
export default {
getASTFromSource,
getASTCached,
clearCache,
};