1const GENERAL_TOOLING_PREFIXES: &[&str] = &[
9 "@types/",
10 "husky",
11 "lint-staged",
12 "commitlint",
13 "@commitlint",
14 "stylelint",
15 "@vitest/",
16 "@jest/",
17 "@tapjs/",
18 "@testing-library/",
19 "@playwright/",
20 "@react-native-community/cli",
21 "@react-native/",
22 "secretlint",
23 "@secretlint/",
24 "oxlint",
25 "@semantic-release/",
26 "semantic-release",
27 "@release-it/",
28 "@lerna-lite/",
29 "@changesets/",
30 "@graphql-codegen/",
31 "@biomejs/",
32 "@electron-forge/",
33 "@electron/",
34 "@formatjs/",
35];
36
37const GENERAL_TOOLING_EXACT: &[&str] = &[
39 "typescript",
40 "prettier",
41 "turbo",
42 "concurrently",
43 "cross-env",
44 "rimraf",
45 "npm-run-all",
46 "npm-run-all2",
47 "nodemon",
48 "ts-node",
49 "tsx",
50 "knip",
51 "fallow",
52 "jest",
53 "vitest",
54 "tap",
55 "happy-dom",
56 "jsdom",
57 "vite",
58 "sass",
59 "sass-embedded",
60 "webpack",
61 "webpack-cli",
62 "webpack-dev-server",
63 "esbuild",
64 "rollup",
65 "swc",
66 "@swc/core",
67 "@swc/jest",
68 "terser",
69 "cssnano",
70 "sharp",
71 "release-it",
72 "lerna",
73 "dotenv-cli",
74 "dotenv-flow",
75 "oxfmt",
76 "jscpd",
77 "npm-check-updates",
78 "markdownlint-cli",
79 "npm-package-json-lint",
80 "synp",
81 "flow-bin",
82 "i18next-parser",
83 "i18next-conv",
84 "webpack-bundle-analyzer",
85 "vite-plugin-svgr",
86 "vite-plugin-eslint",
87 "@vitejs/plugin-vue",
88 "@vitejs/plugin-react",
89 "next-sitemap",
90 "tsup",
91 "unbuild",
92 "typedoc",
93 "nx",
94 "@manypkg/cli",
95 "vue-tsc",
96 "@vue/tsconfig",
97 "@tsconfig/node20",
98 "@tsconfig/react-native",
99 "@typescript/native-preview",
100 "tw-animate-css",
101 "@ianvs/prettier-plugin-sort-imports",
102 "prettier-plugin-tailwindcss",
103 "prettier-plugin-organize-imports",
104 "@vitejs/plugin-react-swc",
105 "@vitejs/plugin-legacy",
106 "rolldown",
107 "rolldown-vite",
108 "oxc-transform",
109 "puppeteer",
110 "madge",
111 "patch-package",
112 "electron",
113 "electron-builder",
114 "electron-vite",
115];
116
117fn tooling_exact_set() -> &'static rustc_hash::FxHashSet<&'static str> {
119 static SET: std::sync::OnceLock<rustc_hash::FxHashSet<&'static str>> =
120 std::sync::OnceLock::new();
121 SET.get_or_init(|| GENERAL_TOOLING_EXACT.iter().copied().collect())
122}
123
124#[must_use]
130pub fn is_known_tooling_dependency(name: &str) -> bool {
131 GENERAL_TOOLING_PREFIXES.iter().any(|p| name.starts_with(p))
132 || tooling_exact_set().contains(name)
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
142 fn types_prefix_matches_scoped() {
143 assert!(is_known_tooling_dependency("@types/node"));
144 assert!(is_known_tooling_dependency("@types/react"));
145 assert!(is_known_tooling_dependency("@types/express"));
146 }
147
148 #[test]
149 fn types_prefix_does_not_match_similar_names() {
150 assert!(!is_known_tooling_dependency("type-fest"));
152 assert!(!is_known_tooling_dependency("typesafe-actions"));
153 }
154
155 #[test]
156 fn storybook_not_blanket_matched() {
157 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_not_blanket_matched() {
171 assert!(!is_known_tooling_dependency("@babel/core"));
173 assert!(!is_known_tooling_dependency("@babel/preset-env"));
174 assert!(!is_known_tooling_dependency("babel-loader"));
175 assert!(!is_known_tooling_dependency("babel-jest"));
176 }
177
178 #[test]
179 fn vitest_prefix_matches() {
180 assert!(is_known_tooling_dependency("@vitest/coverage-v8"));
181 assert!(is_known_tooling_dependency("@vitest/ui"));
182 }
183
184 #[test]
185 fn eslint_not_blanket_matched() {
186 assert!(!is_known_tooling_dependency("eslint"));
188 assert!(!is_known_tooling_dependency("eslint-plugin-react"));
189 assert!(!is_known_tooling_dependency("eslint-config-next"));
190 assert!(!is_known_tooling_dependency("@typescript-eslint/parser"));
191 }
192
193 #[test]
194 fn biomejs_prefix_matches() {
195 assert!(is_known_tooling_dependency("@biomejs/biome"));
196 }
197
198 #[test]
201 fn exact_typescript_matches() {
202 assert!(is_known_tooling_dependency("typescript"));
203 }
204
205 #[test]
206 fn exact_prettier_matches() {
207 assert!(is_known_tooling_dependency("prettier"));
208 }
209
210 #[test]
211 fn exact_vitest_matches() {
212 assert!(is_known_tooling_dependency("vitest"));
213 }
214
215 #[test]
216 fn exact_jest_matches() {
217 assert!(is_known_tooling_dependency("jest"));
218 }
219
220 #[test]
221 fn exact_vite_matches() {
222 assert!(is_known_tooling_dependency("vite"));
223 }
224
225 #[test]
226 fn exact_esbuild_matches() {
227 assert!(is_known_tooling_dependency("esbuild"));
228 }
229
230 #[test]
231 fn exact_tsup_matches() {
232 assert!(is_known_tooling_dependency("tsup"));
233 }
234
235 #[test]
236 fn exact_turbo_matches() {
237 assert!(is_known_tooling_dependency("turbo"));
238 }
239
240 #[test]
243 fn common_runtime_deps_not_tooling() {
244 assert!(!is_known_tooling_dependency("react"));
245 assert!(!is_known_tooling_dependency("react-dom"));
246 assert!(!is_known_tooling_dependency("express"));
247 assert!(!is_known_tooling_dependency("lodash"));
248 assert!(!is_known_tooling_dependency("next"));
249 assert!(!is_known_tooling_dependency("vue"));
250 assert!(!is_known_tooling_dependency("axios"));
251 }
252
253 #[test]
254 fn empty_string_not_tooling() {
255 assert!(!is_known_tooling_dependency(""));
256 }
257
258 #[test]
259 fn near_miss_not_tooling() {
260 assert!(!is_known_tooling_dependency("type-fest"));
262 assert!(!is_known_tooling_dependency("typestyle"));
263 assert!(!is_known_tooling_dependency("prettier-bytes")); }
268
269 #[test]
270 fn sass_variants_are_tooling() {
271 assert!(is_known_tooling_dependency("sass"));
272 assert!(is_known_tooling_dependency("sass-embedded"));
273 }
274
275 #[test]
276 fn prettier_plugins_are_tooling() {
277 assert!(is_known_tooling_dependency(
278 "@ianvs/prettier-plugin-sort-imports"
279 ));
280 assert!(is_known_tooling_dependency("prettier-plugin-tailwindcss"));
281 }
282
283 #[test]
286 fn electron_forge_prefix_matches() {
287 assert!(is_known_tooling_dependency("@electron-forge/cli"));
288 assert!(is_known_tooling_dependency(
289 "@electron-forge/maker-squirrel"
290 ));
291 }
292
293 #[test]
294 fn electron_prefix_matches() {
295 assert!(is_known_tooling_dependency("@electron/rebuild"));
296 assert!(is_known_tooling_dependency("@electron/notarize"));
297 }
298
299 #[test]
300 fn formatjs_prefix_matches() {
301 assert!(is_known_tooling_dependency("@formatjs/cli"));
302 assert!(is_known_tooling_dependency("@formatjs/intl"));
303 }
304
305 #[test]
306 fn rollup_not_blanket_matched() {
307 assert!(!is_known_tooling_dependency("@rollup/plugin-commonjs"));
309 assert!(!is_known_tooling_dependency("@rollup/plugin-node-resolve"));
310 assert!(!is_known_tooling_dependency("@rollup/plugin-typescript"));
311 }
312
313 #[test]
314 fn semantic_release_prefix_matches() {
315 assert!(is_known_tooling_dependency("@semantic-release/github"));
316 assert!(is_known_tooling_dependency("@semantic-release/npm"));
317 assert!(is_known_tooling_dependency("semantic-release"));
318 }
319
320 #[test]
321 fn release_it_prefix_matches() {
322 assert!(is_known_tooling_dependency(
323 "@release-it/conventional-changelog"
324 ));
325 }
326
327 #[test]
328 fn lerna_lite_prefix_matches() {
329 assert!(is_known_tooling_dependency("@lerna-lite/cli"));
330 assert!(is_known_tooling_dependency("@lerna-lite/publish"));
331 }
332
333 #[test]
334 fn changesets_prefix_matches() {
335 assert!(is_known_tooling_dependency("@changesets/cli"));
336 assert!(is_known_tooling_dependency("@changesets/changelog-github"));
337 }
338
339 #[test]
340 fn graphql_codegen_prefix_matches() {
341 assert!(is_known_tooling_dependency("@graphql-codegen/cli"));
342 assert!(is_known_tooling_dependency(
343 "@graphql-codegen/typescript-operations"
344 ));
345 }
346
347 #[test]
348 fn secretlint_prefix_matches() {
349 assert!(is_known_tooling_dependency("secretlint"));
350 assert!(is_known_tooling_dependency(
351 "@secretlint/secretlint-rule-preset-recommend"
352 ));
353 }
354
355 #[test]
356 fn oxlint_prefix_matches() {
357 assert!(is_known_tooling_dependency("oxlint"));
358 }
359
360 #[test]
361 fn react_native_community_prefix_matches() {
362 assert!(is_known_tooling_dependency("@react-native-community/cli"));
363 assert!(is_known_tooling_dependency(
364 "@react-native-community/cli-platform-android"
365 ));
366 }
367
368 #[test]
369 fn react_native_prefix_matches() {
370 assert!(is_known_tooling_dependency("@react-native/metro-config"));
371 assert!(is_known_tooling_dependency(
372 "@react-native/typescript-config"
373 ));
374 }
375
376 #[test]
377 fn jest_prefix_matches() {
378 assert!(is_known_tooling_dependency("@jest/globals"));
379 assert!(is_known_tooling_dependency("@jest/types"));
380 }
381
382 #[test]
383 fn playwright_prefix_matches() {
384 assert!(is_known_tooling_dependency("@playwright/test"));
385 }
386
387 #[test]
388 fn tapjs_prefix_matches() {
389 assert!(is_known_tooling_dependency("@tapjs/test"));
390 assert!(is_known_tooling_dependency("@tapjs/snapshot"));
391 }
392
393 #[test]
396 fn exact_tap_matches() {
397 assert!(is_known_tooling_dependency("tap"));
398 }
399
400 #[test]
401 fn exact_rolldown_matches() {
402 assert!(is_known_tooling_dependency("rolldown"));
403 assert!(is_known_tooling_dependency("rolldown-vite"));
404 }
405
406 #[test]
407 fn exact_electron_matches() {
408 assert!(is_known_tooling_dependency("electron"));
409 assert!(is_known_tooling_dependency("electron-builder"));
410 assert!(is_known_tooling_dependency("electron-vite"));
411 }
412
413 #[test]
414 fn exact_sharp_matches() {
415 assert!(is_known_tooling_dependency("sharp"));
416 }
417
418 #[test]
419 fn exact_puppeteer_matches() {
420 assert!(is_known_tooling_dependency("puppeteer"));
421 }
422
423 #[test]
424 fn exact_madge_matches() {
425 assert!(is_known_tooling_dependency("madge"));
426 }
427
428 #[test]
429 fn exact_patch_package_matches() {
430 assert!(is_known_tooling_dependency("patch-package"));
431 }
432
433 #[test]
434 fn exact_nx_matches() {
435 assert!(is_known_tooling_dependency("nx"));
436 }
437
438 #[test]
439 fn exact_vue_tsc_matches() {
440 assert!(is_known_tooling_dependency("vue-tsc"));
441 }
442
443 #[test]
444 fn exact_tsconfig_packages_match() {
445 assert!(is_known_tooling_dependency("@tsconfig/node20"));
446 assert!(is_known_tooling_dependency("@tsconfig/react-native"));
447 assert!(is_known_tooling_dependency("@vue/tsconfig"));
448 }
449
450 #[test]
451 fn exact_vitejs_plugins_match() {
452 assert!(is_known_tooling_dependency("@vitejs/plugin-vue"));
453 assert!(is_known_tooling_dependency("@vitejs/plugin-react"));
454 assert!(is_known_tooling_dependency("@vitejs/plugin-react-swc"));
455 assert!(is_known_tooling_dependency("@vitejs/plugin-legacy"));
456 }
457
458 #[test]
459 fn exact_oxc_transform_matches() {
460 assert!(is_known_tooling_dependency("oxc-transform"));
461 }
462
463 #[test]
464 fn exact_typescript_native_preview_matches() {
465 assert!(is_known_tooling_dependency("@typescript/native-preview"));
466 }
467
468 #[test]
469 fn exact_tw_animate_css_matches() {
470 assert!(is_known_tooling_dependency("tw-animate-css"));
471 }
472
473 #[test]
474 fn exact_manypkg_cli_matches() {
475 assert!(is_known_tooling_dependency("@manypkg/cli"));
476 }
477
478 #[test]
479 fn exact_swc_variants_match() {
480 assert!(is_known_tooling_dependency("@swc/core"));
481 assert!(is_known_tooling_dependency("@swc/jest"));
482 }
483
484 #[test]
487 fn runtime_deps_with_similar_names_not_tooling() {
488 assert!(!is_known_tooling_dependency("react-scripts"));
490 assert!(!is_known_tooling_dependency("express-validator"));
491 assert!(!is_known_tooling_dependency("sass-loader")); }
493
494 #[test]
495 fn postcss_not_blanket_matched() {
496 assert!(!is_known_tooling_dependency("postcss-modules"));
499 assert!(!is_known_tooling_dependency("postcss-import"));
500 assert!(!is_known_tooling_dependency("autoprefixer"));
501 assert!(!is_known_tooling_dependency("tailwindcss"));
502 assert!(!is_known_tooling_dependency("@tailwindcss/typography"));
503 }
504
505 #[test]
506 fn tooling_exact_set_is_deterministic() {
507 let set1 = tooling_exact_set();
509 let set2 = tooling_exact_set();
510 assert_eq!(set1.len(), set2.len());
511 assert!(set1.contains("typescript"));
512 }
513}