Skip to main content

fallow_core/plugins/
tooling.rs

1//! General tooling dependency detection.
2//!
3//! Known dev dependencies that are tooling (used by CLI/config, not imported in
4//! application code). These complement the per-plugin `tooling_dependencies()`
5//! lists with dependencies that aren't tied to any single plugin.
6
7/// Prefixes of package names that are always dev tooling.
8const GENERAL_TOOLING_PREFIXES: &[&str] = &[
9    "@types/",
10    "eslint",
11    "@typescript-eslint",
12    "husky",
13    "lint-staged",
14    "commitlint",
15    "@commitlint",
16    "stylelint",
17    "postcss",
18    "autoprefixer",
19    "tailwindcss",
20    "@tailwindcss",
21    "@vitest/",
22    "@jest/",
23    "@testing-library/",
24    "@playwright/",
25    "@storybook/",
26    "storybook",
27    "@babel/",
28    "babel-",
29    "@react-native-community/cli",
30    "@react-native/",
31    "secretlint",
32    "@secretlint/",
33    "oxlint",
34    "@semantic-release/",
35    "semantic-release",
36    "@release-it/",
37    "@lerna-lite/",
38    "@changesets/",
39    "@graphql-codegen/",
40    "@rollup/",
41    "@biomejs/",
42    "@electron-forge/",
43    "@electron/",
44    "@formatjs/",
45];
46
47/// Exact package names that are always dev tooling.
48const GENERAL_TOOLING_EXACT: &[&str] = &[
49    "typescript",
50    "prettier",
51    "turbo",
52    "concurrently",
53    "cross-env",
54    "rimraf",
55    "npm-run-all",
56    "npm-run-all2",
57    "nodemon",
58    "ts-node",
59    "tsx",
60    "knip",
61    "fallow",
62    "jest",
63    "vitest",
64    "happy-dom",
65    "jsdom",
66    "vite",
67    "sass",
68    "sass-embedded",
69    "webpack",
70    "webpack-cli",
71    "webpack-dev-server",
72    "esbuild",
73    "rollup",
74    "swc",
75    "@swc/core",
76    "@swc/jest",
77    "terser",
78    "cssnano",
79    "sharp",
80    "release-it",
81    "lerna",
82    "dotenv-cli",
83    "dotenv-flow",
84    "oxfmt",
85    "jscpd",
86    "npm-check-updates",
87    "markdownlint-cli",
88    "npm-package-json-lint",
89    "synp",
90    "flow-bin",
91    "i18next-parser",
92    "i18next-conv",
93    "webpack-bundle-analyzer",
94    "vite-plugin-svgr",
95    "vite-plugin-eslint",
96    "@vitejs/plugin-vue",
97    "@vitejs/plugin-react",
98    "next-sitemap",
99    "tsup",
100    "unbuild",
101    "typedoc",
102    "nx",
103    "@manypkg/cli",
104    "vue-tsc",
105    "@vue/tsconfig",
106    "@tsconfig/node20",
107    "@tsconfig/react-native",
108    "@typescript/native-preview",
109    "tw-animate-css",
110    "@ianvs/prettier-plugin-sort-imports",
111    "prettier-plugin-tailwindcss",
112    "prettier-plugin-organize-imports",
113    "@vitejs/plugin-react-swc",
114    "@vitejs/plugin-legacy",
115    "rolldown",
116    "rolldown-vite",
117    "oxc-transform",
118    "puppeteer",
119    "madge",
120    "patch-package",
121    "electron",
122    "electron-builder",
123    "electron-vite",
124];
125
126/// Check whether a package is a known tooling/dev dependency by name.
127///
128/// This is the single source of truth for general tooling detection.
129/// Per-plugin tooling dependencies are declared via `Plugin::tooling_dependencies()`
130/// and aggregated separately in `AggregatedPluginResult`.
131pub fn is_known_tooling_dependency(name: &str) -> bool {
132    GENERAL_TOOLING_PREFIXES.iter().any(|p| name.starts_with(p))
133        || GENERAL_TOOLING_EXACT.contains(&name)
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    // ── Prefix matching ──────────────────────────────────────────
141
142    #[test]
143    fn types_prefix_matches_scoped() {
144        assert!(is_known_tooling_dependency("@types/node"));
145        assert!(is_known_tooling_dependency("@types/react"));
146        assert!(is_known_tooling_dependency("@types/express"));
147    }
148
149    #[test]
150    fn types_prefix_does_not_match_similar_names() {
151        // "type-fest" should NOT match "@types/" prefix
152        assert!(!is_known_tooling_dependency("type-fest"));
153        assert!(!is_known_tooling_dependency("typesafe-actions"));
154    }
155
156    #[test]
157    fn storybook_prefix_matches() {
158        assert!(is_known_tooling_dependency("@storybook/react"));
159        assert!(is_known_tooling_dependency("@storybook/addon-essentials"));
160        assert!(is_known_tooling_dependency("storybook"));
161    }
162
163    #[test]
164    fn testing_library_prefix_matches() {
165        assert!(is_known_tooling_dependency("@testing-library/react"));
166        assert!(is_known_tooling_dependency("@testing-library/jest-dom"));
167    }
168
169    #[test]
170    fn babel_prefix_matches() {
171        assert!(is_known_tooling_dependency("@babel/core"));
172        assert!(is_known_tooling_dependency("babel-loader"));
173        assert!(is_known_tooling_dependency("babel-jest"));
174    }
175
176    #[test]
177    fn vitest_prefix_matches() {
178        assert!(is_known_tooling_dependency("@vitest/coverage-v8"));
179        assert!(is_known_tooling_dependency("@vitest/ui"));
180    }
181
182    #[test]
183    fn eslint_prefix_matches() {
184        assert!(is_known_tooling_dependency("eslint"));
185        assert!(is_known_tooling_dependency("eslint-plugin-react"));
186        assert!(is_known_tooling_dependency("eslint-config-next"));
187    }
188
189    #[test]
190    fn biomejs_prefix_matches() {
191        assert!(is_known_tooling_dependency("@biomejs/biome"));
192    }
193
194    // ── Exact matching ───────────────────────────────────────────
195
196    #[test]
197    fn exact_typescript_matches() {
198        assert!(is_known_tooling_dependency("typescript"));
199    }
200
201    #[test]
202    fn exact_prettier_matches() {
203        assert!(is_known_tooling_dependency("prettier"));
204    }
205
206    #[test]
207    fn exact_vitest_matches() {
208        assert!(is_known_tooling_dependency("vitest"));
209    }
210
211    #[test]
212    fn exact_jest_matches() {
213        assert!(is_known_tooling_dependency("jest"));
214    }
215
216    #[test]
217    fn exact_vite_matches() {
218        assert!(is_known_tooling_dependency("vite"));
219    }
220
221    #[test]
222    fn exact_esbuild_matches() {
223        assert!(is_known_tooling_dependency("esbuild"));
224    }
225
226    #[test]
227    fn exact_tsup_matches() {
228        assert!(is_known_tooling_dependency("tsup"));
229    }
230
231    #[test]
232    fn exact_turbo_matches() {
233        assert!(is_known_tooling_dependency("turbo"));
234    }
235
236    // ── Non-tooling dependencies ─────────────────────────────────
237
238    #[test]
239    fn common_runtime_deps_not_tooling() {
240        assert!(!is_known_tooling_dependency("react"));
241        assert!(!is_known_tooling_dependency("react-dom"));
242        assert!(!is_known_tooling_dependency("express"));
243        assert!(!is_known_tooling_dependency("lodash"));
244        assert!(!is_known_tooling_dependency("next"));
245        assert!(!is_known_tooling_dependency("vue"));
246        assert!(!is_known_tooling_dependency("axios"));
247    }
248
249    #[test]
250    fn empty_string_not_tooling() {
251        assert!(!is_known_tooling_dependency(""));
252    }
253
254    #[test]
255    fn near_miss_not_tooling() {
256        // These look similar to tooling but should NOT match
257        assert!(!is_known_tooling_dependency("type-fest"));
258        assert!(!is_known_tooling_dependency("typestyle"));
259        assert!(!is_known_tooling_dependency("prettier-bytes")); // not the exact "prettier"
260        // Note: "prettier-bytes" starts with "prettier" but only prefix matches
261        // check the prefixes list — "prettier" is NOT in GENERAL_TOOLING_PREFIXES,
262        // it's in GENERAL_TOOLING_EXACT. So "prettier-bytes" should not match.
263    }
264
265    #[test]
266    fn sass_variants_are_tooling() {
267        assert!(is_known_tooling_dependency("sass"));
268        assert!(is_known_tooling_dependency("sass-embedded"));
269    }
270
271    #[test]
272    fn prettier_plugins_are_tooling() {
273        assert!(is_known_tooling_dependency(
274            "@ianvs/prettier-plugin-sort-imports"
275        ));
276        assert!(is_known_tooling_dependency("prettier-plugin-tailwindcss"));
277    }
278}