dependency-cruiser
Version:
Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.
118 lines (105 loc) • 3.66 kB
JavaScript
import { isAbsolute, resolve as path_resolve } from "node:path";
import getExtension from "../../utl/get-extension.mjs";
let gFollowableExtensionsCache = new Set();
let gFollowableExtensionsCacheInitialized = false;
export function isScoped(pModuleName) {
return pModuleName.startsWith("@");
}
export function isRelativeModuleName(pModuleName) {
return (
pModuleName.startsWith("./") ||
pModuleName.startsWith("../") ||
pModuleName === "." ||
pModuleName === ".."
// note ".blah" and "..blah" are _not_ relative
);
}
export function isExternalModule(
pResolvedModuleName,
pModuleFolderNames = ["node_modules"],
pBaseDirectory = "."
) {
return (
Boolean(pResolvedModuleName) &&
pModuleFolderNames.some(
// pModules can contain relative paths, but also absolute ones.
// WebPack treats these differently:
// - absolute paths only match that exact path
// - relative paths get a node lookup treatment so "turtle" matches
// "turtle", "../turtle", "../../turtle", "../../../turtle" (.. =>
// turtles all the way down)
// hence we'll have to test for them in different fashion as well.
// reference: https://webpack.js.org/configuration/resolve/#resolve-modules
(pModuleFolderName) => {
if (isAbsolute(pModuleFolderName)) {
return path_resolve(pBaseDirectory, pResolvedModuleName).startsWith(
pModuleFolderName
);
}
return pResolvedModuleName.includes(pModuleFolderName);
}
)
);
}
function determineFollowableExtensions(pResolveOptions) {
let lReturnValue = new Set(pResolveOptions.extensions);
// we could include things like pictures, movies, html, xml
// etc in lKnownUnfollowables as well. Typically in
// javascript-like sources you don't import non-javascript
// stuff without mentioning the extension (`import 'styles.scss`
// is more clear than`import 'styles'` as you'd expect that
// to resolve to something javascript-like.
// Defensively added the stylesheetlanguages here explicitly
// nonetheless - they can contain import statements and the
// fallback javascript parser will happily parse them, which
// will result in false positives.
const lKnownUnfollowables = [
".json",
".node",
".css",
".sass",
".scss",
".stylus",
".less",
];
lKnownUnfollowables.forEach((pUnfollowable) => {
lReturnValue.delete(pUnfollowable);
});
return lReturnValue;
}
function getFollowableExtensions(pResolveOptions) {
if (!gFollowableExtensionsCacheInitialized || pResolveOptions.bustTheCache) {
gFollowableExtensionsCache = determineFollowableExtensions(pResolveOptions);
gFollowableExtensionsCacheInitialized = true;
}
return gFollowableExtensionsCache;
}
export function isFollowable(pResolvedFilename, pResolveOptions) {
return getFollowableExtensions(pResolveOptions).has(
getExtension(pResolvedFilename)
);
}
function isWebPackAliased(pModuleName, pAliasObject) {
return Object.keys(pAliasObject || {}).some((pAliasLHS) =>
pModuleName.startsWith(pAliasLHS)
);
}
function isLikelyTSAliased(
pModuleName,
pResolvedModuleName,
pResolveOptions,
pBaseDirectory
) {
return (
pResolveOptions.tsConfig &&
!isRelativeModuleName(pModuleName) &&
pResolvedModuleName &&
!isExternalModule(pResolvedModuleName, ["node_modules"], pBaseDirectory)
);
}
export function isAliassy(pModuleName, pResolvedModuleName, pResolveOptions) {
return (
isWebPackAliased(pModuleName, pResolveOptions.alias) ||
isLikelyTSAliased(pModuleName, pResolvedModuleName, pResolveOptions)
);
}