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