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 "playwright",
110 "puppeteer",
111 "madge",
112 "patch-package",
113 "electron",
114 "electron-builder",
115 "electron-vite",
116];
117
118fn tooling_exact_set() -> &'static rustc_hash::FxHashSet<&'static str> {
120 static SET: std::sync::OnceLock<rustc_hash::FxHashSet<&'static str>> =
121 std::sync::OnceLock::new();
122 SET.get_or_init(|| GENERAL_TOOLING_EXACT.iter().copied().collect())
123}
124
125#[must_use]
131pub fn is_known_tooling_dependency(name: &str) -> bool {
132 GENERAL_TOOLING_PREFIXES.iter().any(|p| name.starts_with(p))
133 || tooling_exact_set().contains(name)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[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 assert!(!is_known_tooling_dependency("type-fest"));
153 assert!(!is_known_tooling_dependency("typesafe-actions"));
154 }
155
156 #[test]
157 fn storybook_not_blanket_matched() {
158 assert!(!is_known_tooling_dependency("@storybook/react"));
160 assert!(!is_known_tooling_dependency("@storybook/addon-essentials"));
161 assert!(!is_known_tooling_dependency("storybook"));
162 }
163
164 #[test]
165 fn testing_library_prefix_matches() {
166 assert!(is_known_tooling_dependency("@testing-library/react"));
167 assert!(is_known_tooling_dependency("@testing-library/jest-dom"));
168 }
169
170 #[test]
171 fn babel_not_blanket_matched() {
172 assert!(!is_known_tooling_dependency("@babel/core"));
174 assert!(!is_known_tooling_dependency("@babel/preset-env"));
175 assert!(!is_known_tooling_dependency("babel-loader"));
176 assert!(!is_known_tooling_dependency("babel-jest"));
177 }
178
179 #[test]
180 fn vitest_prefix_matches() {
181 assert!(is_known_tooling_dependency("@vitest/coverage-v8"));
182 assert!(is_known_tooling_dependency("@vitest/ui"));
183 }
184
185 #[test]
186 fn eslint_not_blanket_matched() {
187 assert!(!is_known_tooling_dependency("eslint"));
189 assert!(!is_known_tooling_dependency("eslint-plugin-react"));
190 assert!(!is_known_tooling_dependency("eslint-config-next"));
191 assert!(!is_known_tooling_dependency("@typescript-eslint/parser"));
192 }
193
194 #[test]
195 fn biomejs_prefix_matches() {
196 assert!(is_known_tooling_dependency("@biomejs/biome"));
197 }
198
199 #[test]
202 fn exact_typescript_matches() {
203 assert!(is_known_tooling_dependency("typescript"));
204 }
205
206 #[test]
207 fn exact_prettier_matches() {
208 assert!(is_known_tooling_dependency("prettier"));
209 }
210
211 #[test]
212 fn exact_vitest_matches() {
213 assert!(is_known_tooling_dependency("vitest"));
214 }
215
216 #[test]
217 fn exact_jest_matches() {
218 assert!(is_known_tooling_dependency("jest"));
219 }
220
221 #[test]
222 fn exact_vite_matches() {
223 assert!(is_known_tooling_dependency("vite"));
224 }
225
226 #[test]
227 fn exact_esbuild_matches() {
228 assert!(is_known_tooling_dependency("esbuild"));
229 }
230
231 #[test]
232 fn exact_tsup_matches() {
233 assert!(is_known_tooling_dependency("tsup"));
234 }
235
236 #[test]
237 fn exact_turbo_matches() {
238 assert!(is_known_tooling_dependency("turbo"));
239 }
240
241 #[test]
244 fn common_runtime_deps_not_tooling() {
245 assert!(!is_known_tooling_dependency("react"));
246 assert!(!is_known_tooling_dependency("react-dom"));
247 assert!(!is_known_tooling_dependency("express"));
248 assert!(!is_known_tooling_dependency("lodash"));
249 assert!(!is_known_tooling_dependency("next"));
250 assert!(!is_known_tooling_dependency("vue"));
251 assert!(!is_known_tooling_dependency("axios"));
252 }
253
254 #[test]
255 fn empty_string_not_tooling() {
256 assert!(!is_known_tooling_dependency(""));
257 }
258
259 #[test]
260 fn near_miss_not_tooling() {
261 assert!(!is_known_tooling_dependency("type-fest"));
263 assert!(!is_known_tooling_dependency("typestyle"));
264 assert!(!is_known_tooling_dependency("prettier-bytes")); }
269
270 #[test]
271 fn sass_variants_are_tooling() {
272 assert!(is_known_tooling_dependency("sass"));
273 assert!(is_known_tooling_dependency("sass-embedded"));
274 }
275
276 #[test]
277 fn prettier_plugins_are_tooling() {
278 assert!(is_known_tooling_dependency(
279 "@ianvs/prettier-plugin-sort-imports"
280 ));
281 assert!(is_known_tooling_dependency("prettier-plugin-tailwindcss"));
282 }
283
284 #[test]
287 fn electron_forge_prefix_matches() {
288 assert!(is_known_tooling_dependency("@electron-forge/cli"));
289 assert!(is_known_tooling_dependency(
290 "@electron-forge/maker-squirrel"
291 ));
292 }
293
294 #[test]
295 fn electron_prefix_matches() {
296 assert!(is_known_tooling_dependency("@electron/rebuild"));
297 assert!(is_known_tooling_dependency("@electron/notarize"));
298 }
299
300 #[test]
301 fn formatjs_prefix_matches() {
302 assert!(is_known_tooling_dependency("@formatjs/cli"));
303 assert!(is_known_tooling_dependency("@formatjs/intl"));
304 }
305
306 #[test]
307 fn rollup_not_blanket_matched() {
308 assert!(!is_known_tooling_dependency("@rollup/plugin-commonjs"));
310 assert!(!is_known_tooling_dependency("@rollup/plugin-node-resolve"));
311 assert!(!is_known_tooling_dependency("@rollup/plugin-typescript"));
312 }
313
314 #[test]
315 fn semantic_release_prefix_matches() {
316 assert!(is_known_tooling_dependency("@semantic-release/github"));
317 assert!(is_known_tooling_dependency("@semantic-release/npm"));
318 assert!(is_known_tooling_dependency("semantic-release"));
319 }
320
321 #[test]
322 fn release_it_prefix_matches() {
323 assert!(is_known_tooling_dependency(
324 "@release-it/conventional-changelog"
325 ));
326 }
327
328 #[test]
329 fn lerna_lite_prefix_matches() {
330 assert!(is_known_tooling_dependency("@lerna-lite/cli"));
331 assert!(is_known_tooling_dependency("@lerna-lite/publish"));
332 }
333
334 #[test]
335 fn changesets_prefix_matches() {
336 assert!(is_known_tooling_dependency("@changesets/cli"));
337 assert!(is_known_tooling_dependency("@changesets/changelog-github"));
338 }
339
340 #[test]
341 fn graphql_codegen_prefix_matches() {
342 assert!(is_known_tooling_dependency("@graphql-codegen/cli"));
343 assert!(is_known_tooling_dependency(
344 "@graphql-codegen/typescript-operations"
345 ));
346 }
347
348 #[test]
349 fn secretlint_prefix_matches() {
350 assert!(is_known_tooling_dependency("secretlint"));
351 assert!(is_known_tooling_dependency(
352 "@secretlint/secretlint-rule-preset-recommend"
353 ));
354 }
355
356 #[test]
357 fn oxlint_prefix_matches() {
358 assert!(is_known_tooling_dependency("oxlint"));
359 }
360
361 #[test]
362 fn react_native_community_prefix_matches() {
363 assert!(is_known_tooling_dependency("@react-native-community/cli"));
364 assert!(is_known_tooling_dependency(
365 "@react-native-community/cli-platform-android"
366 ));
367 }
368
369 #[test]
370 fn react_native_prefix_matches() {
371 assert!(is_known_tooling_dependency("@react-native/metro-config"));
372 assert!(is_known_tooling_dependency(
373 "@react-native/typescript-config"
374 ));
375 }
376
377 #[test]
378 fn jest_prefix_matches() {
379 assert!(is_known_tooling_dependency("@jest/globals"));
380 assert!(is_known_tooling_dependency("@jest/types"));
381 }
382
383 #[test]
384 fn playwright_prefix_matches() {
385 assert!(is_known_tooling_dependency("@playwright/test"));
386 assert!(is_known_tooling_dependency("playwright"));
387 }
388
389 #[test]
390 fn tapjs_prefix_matches() {
391 assert!(is_known_tooling_dependency("@tapjs/test"));
392 assert!(is_known_tooling_dependency("@tapjs/snapshot"));
393 }
394
395 #[test]
398 fn exact_tap_matches() {
399 assert!(is_known_tooling_dependency("tap"));
400 }
401
402 #[test]
403 fn exact_rolldown_matches() {
404 assert!(is_known_tooling_dependency("rolldown"));
405 assert!(is_known_tooling_dependency("rolldown-vite"));
406 }
407
408 #[test]
409 fn exact_electron_matches() {
410 assert!(is_known_tooling_dependency("electron"));
411 assert!(is_known_tooling_dependency("electron-builder"));
412 assert!(is_known_tooling_dependency("electron-vite"));
413 }
414
415 #[test]
416 fn exact_sharp_matches() {
417 assert!(is_known_tooling_dependency("sharp"));
418 }
419
420 #[test]
421 fn exact_puppeteer_matches() {
422 assert!(is_known_tooling_dependency("puppeteer"));
423 }
424
425 #[test]
426 fn exact_madge_matches() {
427 assert!(is_known_tooling_dependency("madge"));
428 }
429
430 #[test]
431 fn exact_patch_package_matches() {
432 assert!(is_known_tooling_dependency("patch-package"));
433 }
434
435 #[test]
436 fn exact_nx_matches() {
437 assert!(is_known_tooling_dependency("nx"));
438 }
439
440 #[test]
441 fn exact_vue_tsc_matches() {
442 assert!(is_known_tooling_dependency("vue-tsc"));
443 }
444
445 #[test]
446 fn exact_tsconfig_packages_match() {
447 assert!(is_known_tooling_dependency("@tsconfig/node20"));
448 assert!(is_known_tooling_dependency("@tsconfig/react-native"));
449 assert!(is_known_tooling_dependency("@vue/tsconfig"));
450 }
451
452 #[test]
453 fn exact_vitejs_plugins_match() {
454 assert!(is_known_tooling_dependency("@vitejs/plugin-vue"));
455 assert!(is_known_tooling_dependency("@vitejs/plugin-react"));
456 assert!(is_known_tooling_dependency("@vitejs/plugin-react-swc"));
457 assert!(is_known_tooling_dependency("@vitejs/plugin-legacy"));
458 }
459
460 #[test]
461 fn exact_oxc_transform_matches() {
462 assert!(is_known_tooling_dependency("oxc-transform"));
463 }
464
465 #[test]
466 fn exact_typescript_native_preview_matches() {
467 assert!(is_known_tooling_dependency("@typescript/native-preview"));
468 }
469
470 #[test]
471 fn exact_tw_animate_css_matches() {
472 assert!(is_known_tooling_dependency("tw-animate-css"));
473 }
474
475 #[test]
476 fn exact_manypkg_cli_matches() {
477 assert!(is_known_tooling_dependency("@manypkg/cli"));
478 }
479
480 #[test]
481 fn exact_swc_variants_match() {
482 assert!(is_known_tooling_dependency("@swc/core"));
483 assert!(is_known_tooling_dependency("@swc/jest"));
484 }
485
486 #[test]
489 fn runtime_deps_with_similar_names_not_tooling() {
490 assert!(!is_known_tooling_dependency("react-scripts"));
492 assert!(!is_known_tooling_dependency("express-validator"));
493 assert!(!is_known_tooling_dependency("sass-loader")); }
495
496 #[test]
497 fn postcss_not_blanket_matched() {
498 assert!(!is_known_tooling_dependency("postcss-modules"));
501 assert!(!is_known_tooling_dependency("postcss-import"));
502 assert!(!is_known_tooling_dependency("autoprefixer"));
503 assert!(!is_known_tooling_dependency("tailwindcss"));
504 assert!(!is_known_tooling_dependency("@tailwindcss/typography"));
505 }
506
507 #[test]
508 fn tooling_exact_set_is_deterministic() {
509 let set1 = tooling_exact_set();
511 let set2 = tooling_exact_set();
512 assert_eq!(set1.len(), set2.len());
513 assert!(set1.contains("typescript"));
514 }
515}