pub fn is_virtual_module(name: &str) -> bool {
name.starts_with("virtual:")
}
pub fn is_builtin_module(name: &str) -> bool {
if name.starts_with("bun:") {
return true;
}
if name.starts_with("cloudflare:") {
return true;
}
if name.starts_with("sass:") {
return true;
}
if name == "std" || name.starts_with("std/") {
return true;
}
let builtins = [
"assert",
"assert/strict",
"async_hooks",
"buffer",
"child_process",
"cluster",
"console",
"constants",
"crypto",
"dgram",
"diagnostics_channel",
"dns",
"dns/promises",
"domain",
"events",
"fs",
"fs/promises",
"http",
"http2",
"https",
"inspector",
"inspector/promises",
"module",
"net",
"os",
"path",
"path/posix",
"path/win32",
"perf_hooks",
"process",
"punycode",
"querystring",
"readline",
"readline/promises",
"repl",
"stream",
"stream/consumers",
"stream/promises",
"stream/web",
"string_decoder",
"sys",
"test",
"test/reporters",
"timers",
"timers/promises",
"tls",
"trace_events",
"tty",
"url",
"util",
"util/types",
"v8",
"vm",
"wasi",
"worker_threads",
"zlib",
];
let stripped = name.strip_prefix("node:").unwrap_or(name);
builtins.contains(&stripped)
}
pub(in crate::analyze) fn is_implicit_dependency(name: &str) -> bool {
if name.starts_with("@types/") {
return true;
}
let implicit_deps = [
"react-dom",
"react-dom/client",
"react-native",
"@next/font",
"@next/mdx",
"@next/bundle-analyzer",
"@next/env",
"utf-8-validate",
"bufferutil",
];
implicit_deps.contains(&name)
}
pub(in crate::analyze) fn is_path_alias(name: &str) -> bool {
if name.starts_with('#') {
return true;
}
if name.starts_with("~/") || name.starts_with("~~/") || name.starts_with("@@/") {
return true;
}
if name.starts_with("@/") {
return true;
}
if name.starts_with('@') {
let scope = name.split('/').next().unwrap_or(name);
if scope.len() > 1 && scope.chars().nth(1).is_some_and(|c| c.is_ascii_uppercase()) {
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtin_module_fs() {
assert!(is_builtin_module("fs"));
}
#[test]
fn builtin_module_path() {
assert!(is_builtin_module("path"));
}
#[test]
fn builtin_module_with_node_prefix() {
assert!(is_builtin_module("node:fs"));
assert!(is_builtin_module("node:path"));
assert!(is_builtin_module("node:crypto"));
}
#[test]
fn builtin_module_all_known() {
let known = [
"assert",
"buffer",
"child_process",
"cluster",
"console",
"constants",
"crypto",
"dgram",
"dns",
"domain",
"events",
"fs",
"http",
"http2",
"https",
"module",
"net",
"os",
"path",
"perf_hooks",
"process",
"punycode",
"querystring",
"readline",
"repl",
"stream",
"string_decoder",
"sys",
"timers",
"tls",
"tty",
"url",
"util",
"v8",
"vm",
"wasi",
"worker_threads",
"zlib",
];
for name in &known {
assert!(is_builtin_module(name), "{name} should be a builtin module");
}
}
#[test]
fn not_builtin_module() {
assert!(!is_builtin_module("react"));
assert!(!is_builtin_module("lodash"));
assert!(!is_builtin_module("express"));
assert!(!is_builtin_module("@scope/pkg"));
}
#[test]
fn not_builtin_similar_names() {
assert!(!is_builtin_module("filesystem"));
assert!(!is_builtin_module("pathlib"));
assert!(!is_builtin_module("node:react"));
}
#[test]
fn not_builtin_npm_packages_with_builtin_prefix() {
assert!(!is_builtin_module("test-utils/helpers"));
assert!(!is_builtin_module("url-parse"));
assert!(!is_builtin_module("path-browserify"));
assert!(!is_builtin_module("stream-browserify"));
assert!(!is_builtin_module("events-emitter"));
assert!(!is_builtin_module("util-deprecate"));
assert!(!is_builtin_module("os-tmpdir"));
assert!(!is_builtin_module("net-ping"));
}
#[test]
fn implicit_dep_types_packages() {
assert!(is_implicit_dependency("@types/node"));
assert!(is_implicit_dependency("@types/react"));
assert!(is_implicit_dependency("@types/jest"));
}
#[test]
fn not_implicit_dep() {
assert!(!is_implicit_dependency("react"));
assert!(!is_implicit_dependency("@scope/types"));
assert!(!is_implicit_dependency("types"));
assert!(!is_implicit_dependency("typescript"));
assert!(!is_implicit_dependency("prettier"));
assert!(!is_implicit_dependency("eslint"));
}
#[test]
fn tooling_dep_prefixes() {
assert!(crate::plugins::is_known_tooling_dependency("@types/node"));
assert!(crate::plugins::is_known_tooling_dependency("prettier"));
assert!(crate::plugins::is_known_tooling_dependency("husky"));
assert!(crate::plugins::is_known_tooling_dependency("lint-staged"));
assert!(crate::plugins::is_known_tooling_dependency("commitlint"));
assert!(crate::plugins::is_known_tooling_dependency(
"@commitlint/config-conventional"
));
assert!(crate::plugins::is_known_tooling_dependency("stylelint"));
}
#[test]
fn tooling_dep_plugin_handled_not_blanket() {
assert!(!crate::plugins::is_known_tooling_dependency("eslint"));
assert!(!crate::plugins::is_known_tooling_dependency(
"eslint-plugin-react"
));
assert!(!crate::plugins::is_known_tooling_dependency(
"@typescript-eslint/parser"
));
assert!(!crate::plugins::is_known_tooling_dependency("postcss"));
assert!(!crate::plugins::is_known_tooling_dependency("autoprefixer"));
assert!(!crate::plugins::is_known_tooling_dependency("tailwindcss"));
assert!(!crate::plugins::is_known_tooling_dependency(
"@tailwindcss/forms"
));
}
#[test]
fn tooling_dep_exact_matches() {
assert!(crate::plugins::is_known_tooling_dependency("typescript"));
assert!(crate::plugins::is_known_tooling_dependency("prettier"));
assert!(crate::plugins::is_known_tooling_dependency("turbo"));
assert!(crate::plugins::is_known_tooling_dependency("concurrently"));
assert!(crate::plugins::is_known_tooling_dependency("cross-env"));
assert!(crate::plugins::is_known_tooling_dependency("rimraf"));
assert!(crate::plugins::is_known_tooling_dependency("npm-run-all"));
assert!(crate::plugins::is_known_tooling_dependency("nodemon"));
assert!(crate::plugins::is_known_tooling_dependency("ts-node"));
assert!(crate::plugins::is_known_tooling_dependency("tsx"));
}
#[test]
fn not_tooling_dep() {
assert!(!crate::plugins::is_known_tooling_dependency("react"));
assert!(!crate::plugins::is_known_tooling_dependency("next"));
assert!(!crate::plugins::is_known_tooling_dependency("lodash"));
assert!(!crate::plugins::is_known_tooling_dependency("express"));
assert!(!crate::plugins::is_known_tooling_dependency(
"@emotion/react"
));
}
#[test]
fn tooling_dep_testing_frameworks() {
assert!(crate::plugins::is_known_tooling_dependency("jest"));
assert!(crate::plugins::is_known_tooling_dependency("vitest"));
assert!(crate::plugins::is_known_tooling_dependency("@jest/globals"));
assert!(crate::plugins::is_known_tooling_dependency(
"@vitest/coverage-v8"
));
assert!(crate::plugins::is_known_tooling_dependency(
"@testing-library/react"
));
assert!(crate::plugins::is_known_tooling_dependency(
"@testing-library/jest-dom"
));
assert!(crate::plugins::is_known_tooling_dependency(
"@playwright/test"
));
}
#[test]
fn tooling_dep_environments_and_cli() {
assert!(crate::plugins::is_known_tooling_dependency("happy-dom"));
assert!(crate::plugins::is_known_tooling_dependency("jsdom"));
assert!(crate::plugins::is_known_tooling_dependency("knip"));
}
#[test]
fn path_alias_at_slash() {
assert!(is_path_alias("@/components"));
}
#[test]
fn path_alias_tilde() {
assert!(is_path_alias("~/lib"));
}
#[test]
fn path_alias_hash_imports_map() {
assert!(is_path_alias("#internal/module"));
}
#[test]
fn path_alias_pascal_case_scope() {
assert!(is_path_alias("@Components/Button"));
}
#[test]
fn not_path_alias_regular_package() {
assert!(!is_path_alias("react"));
}
#[test]
fn not_path_alias_scoped_npm_package() {
assert!(!is_path_alias("@scope/pkg"));
}
#[test]
fn not_path_alias_emotion_react() {
assert!(!is_path_alias("@emotion/react"));
}
#[test]
fn not_path_alias_lodash() {
assert!(!is_path_alias("lodash"));
}
#[test]
fn not_path_alias_lowercase_short_scope() {
assert!(!is_path_alias("@s/lowercase"));
}
#[test]
fn virtual_module_vite_convention() {
assert!(is_virtual_module("virtual:pwa-register"));
assert!(is_virtual_module("virtual:pwa-register/react"));
assert!(is_virtual_module("virtual:uno.css"));
assert!(is_virtual_module("virtual:unocss"));
assert!(is_virtual_module("virtual:generated-layouts"));
assert!(is_virtual_module("virtual:generated-pages"));
assert!(is_virtual_module("virtual:icons/mdi/home"));
assert!(is_virtual_module("virtual:windi.css"));
assert!(is_virtual_module("virtual:windi-devtools"));
assert!(is_virtual_module("virtual:svg-icons-register"));
assert!(is_virtual_module("virtual:remix/server-build"));
assert!(is_virtual_module("virtual:emoji-mart-lang-importer"));
}
#[test]
fn not_virtual_module() {
assert!(!is_virtual_module("react"));
assert!(!is_virtual_module("lodash"));
assert!(!is_virtual_module("@scope/pkg"));
assert!(!is_virtual_module("node:fs"));
assert!(!is_virtual_module("cloudflare:workers"));
}
#[test]
fn path_alias_pascal_case_scopes() {
assert!(is_path_alias("@Components/Button"));
assert!(is_path_alias("@Hooks/useApi"));
assert!(is_path_alias("@Services/auth"));
assert!(is_path_alias("@Utils/format"));
assert!(is_path_alias("@Lib/helpers"));
}
#[test]
fn path_alias_hash_imports() {
assert!(is_path_alias("#/utils"));
assert!(is_path_alias("#subpath"));
assert!(is_path_alias("#internal/module"));
assert!(is_path_alias("#lib"));
assert!(is_path_alias("#components/Button"));
}
#[test]
fn path_alias_tilde_imports() {
assert!(is_path_alias("~/components"));
assert!(is_path_alias("~/lib/helpers"));
assert!(is_path_alias("~/utils/format"));
assert!(is_path_alias("~/styles/theme"));
assert!(is_path_alias("~~/shared/theme"));
assert!(is_path_alias("@@/shared/theme"));
}
#[test]
fn not_path_alias_bare_tilde() {
assert!(!is_path_alias("~some-package"));
assert!(!is_path_alias("~"));
}
#[test]
fn path_alias_at_slash_subpaths() {
assert!(is_path_alias("@/components"));
assert!(is_path_alias("@/utils/helpers"));
assert!(is_path_alias("@/lib/api/client"));
assert!(is_path_alias("@/styles"));
}
#[test]
fn not_path_alias_regular_npm_packages() {
assert!(!is_path_alias("lodash"));
assert!(!is_path_alias("react"));
assert!(!is_path_alias("express"));
assert!(!is_path_alias("next"));
assert!(!is_path_alias("typescript"));
assert!(!is_path_alias("zod"));
}
#[test]
fn not_path_alias_scoped_npm_packages() {
assert!(!is_path_alias("@types/node"));
assert!(!is_path_alias("@types/react"));
assert!(!is_path_alias("@babel/core"));
assert!(!is_path_alias("@babel/preset-env"));
assert!(!is_path_alias("@emotion/react"));
assert!(!is_path_alias("@emotion/styled"));
assert!(!is_path_alias("@tanstack/react-query"));
assert!(!is_path_alias("@testing-library/react"));
assert!(!is_path_alias("@nestjs/core"));
assert!(!is_path_alias("@prisma/client"));
}
#[test]
fn not_path_alias_edge_case_scopes() {
assert!(!is_path_alias("@s/lowercase"));
assert!(!is_path_alias("@a/package"));
assert!(!is_path_alias("@x/something"));
}
#[test]
fn not_path_alias_bare_at_sign() {
assert!(!is_path_alias("@"));
}
#[test]
fn builtin_module_subpath_imports() {
assert!(is_builtin_module("assert/strict"));
assert!(is_builtin_module("dns/promises"));
assert!(is_builtin_module("fs/promises"));
assert!(is_builtin_module("path/posix"));
assert!(is_builtin_module("path/win32"));
assert!(is_builtin_module("readline/promises"));
assert!(is_builtin_module("stream/consumers"));
assert!(is_builtin_module("stream/promises"));
assert!(is_builtin_module("stream/web"));
assert!(is_builtin_module("timers/promises"));
assert!(is_builtin_module("util/types"));
assert!(is_builtin_module("inspector/promises"));
assert!(is_builtin_module("test/reporters"));
}
#[test]
fn builtin_module_subpath_with_node_prefix() {
assert!(is_builtin_module("node:fs/promises"));
assert!(is_builtin_module("node:path/posix"));
assert!(is_builtin_module("node:stream/web"));
assert!(is_builtin_module("node:timers/promises"));
assert!(is_builtin_module("node:util/types"));
assert!(is_builtin_module("node:test/reporters"));
}
#[test]
fn builtin_module_bun() {
assert!(is_builtin_module("bun:sqlite"));
assert!(is_builtin_module("bun:test"));
assert!(is_builtin_module("bun:ffi"));
assert!(is_builtin_module("bun:jsc"));
}
#[test]
fn builtin_module_cloudflare_workers() {
assert!(is_builtin_module("cloudflare:workers"));
assert!(is_builtin_module("cloudflare:sockets"));
assert!(is_builtin_module("cloudflare:email"));
}
#[test]
fn builtin_module_deno_std() {
assert!(is_builtin_module("std"));
assert!(is_builtin_module("std/path"));
assert!(is_builtin_module("std/fs"));
}
#[test]
fn builtin_module_sass() {
assert!(is_builtin_module("sass:math"));
assert!(is_builtin_module("sass:string"));
assert!(is_builtin_module("sass:color"));
assert!(is_builtin_module("sass:list"));
assert!(is_builtin_module("sass:map"));
assert!(is_builtin_module("sass:meta"));
assert!(is_builtin_module("sass:selector"));
}
#[test]
fn not_builtin_module_sass_like_packages() {
assert!(!is_builtin_module("sass"));
assert!(!is_builtin_module("sass-loader"));
assert!(!is_builtin_module("@types/sass"));
}
#[test]
fn not_builtin_module_fake_subpaths() {
assert!(!is_builtin_module("fs/extra"));
assert!(!is_builtin_module("path/utils"));
assert!(!is_builtin_module("stream/transform"));
}
#[test]
fn virtual_module_edge_cases() {
assert!(is_virtual_module("virtual:"));
assert!(!is_virtual_module(""));
assert!(!is_virtual_module("Virtual:something"));
assert!(!is_virtual_module("VIRTUAL:something"));
}
#[test]
fn implicit_dep_react_dom_and_native() {
assert!(is_implicit_dependency("react-dom"));
assert!(is_implicit_dependency("react-dom/client"));
assert!(is_implicit_dependency("react-native"));
}
#[test]
fn implicit_dep_next_packages() {
assert!(is_implicit_dependency("@next/font"));
assert!(is_implicit_dependency("@next/mdx"));
assert!(is_implicit_dependency("@next/bundle-analyzer"));
assert!(is_implicit_dependency("@next/env"));
}
#[test]
fn implicit_dep_websocket_native_addons() {
assert!(is_implicit_dependency("utf-8-validate"));
assert!(is_implicit_dependency("bufferutil"));
}
#[test]
fn not_implicit_dep_similar_names() {
assert!(!is_implicit_dependency("react"));
assert!(!is_implicit_dependency("react-dom-extra"));
assert!(!is_implicit_dependency("@next/swc"));
assert!(!is_implicit_dependency("react-native-web"));
assert!(!is_implicit_dependency("@types"));
}
#[test]
fn path_alias_hash_prefix() {
assert!(is_path_alias("#internal/module"));
assert!(is_path_alias("#app/utils"));
}
#[test]
fn path_alias_tilde_prefix() {
assert!(is_path_alias("~/store/auth"));
}
#[test]
fn path_alias_at_slash_prefix() {
assert!(is_path_alias("@/hooks/useAuth"));
}
#[test]
fn path_alias_pascal_case_scope_additional() {
assert!(is_path_alias("@Hooks/useAuth"));
assert!(is_path_alias("@Components/Button"));
assert!(is_path_alias("@Services/api"));
}
#[test]
fn not_path_alias_lowercase_scoped_packages() {
assert!(!is_path_alias("@angular/core"));
assert!(!is_path_alias("@emotion/styled"));
assert!(!is_path_alias("@tanstack/react-query"));
}
#[test]
fn not_path_alias_bare_packages() {
assert!(!is_path_alias("react"));
assert!(!is_path_alias("lodash"));
assert!(!is_path_alias("express"));
}
#[test]
fn virtual_module_prefix() {
assert!(is_virtual_module("virtual:pwa-register"));
assert!(is_virtual_module("virtual:uno.css"));
assert!(is_virtual_module("virtual:generated-pages"));
}
#[test]
fn not_virtual_module_non_virtual_imports() {
assert!(!is_virtual_module("react"));
assert!(!is_virtual_module("@virtual/package"));
assert!(!is_virtual_module("./virtual-file"));
}
}