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