diff --git a/README.md b/README.md index c622224bb..4a7ce2789 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ For cleanup-specific findings: npx fallow dead-code ``` -116 framework plugins. No Node.js runtime required for static analysis. No config needed for the first run. +117 framework plugins. No Node.js runtime required for static analysis. No config needed for the first run. ## What is Fallow? @@ -749,7 +749,7 @@ See the [full configuration reference](https://docs.fallow.tools/configuration/o ## Framework plugins -116 built-in plugins detect entry points, convention exports, config-defined aliases, and template-visible usage for your framework automatically. +117 built-in plugins detect entry points, convention exports, config-defined aliases, and template-visible usage for your framework automatically. | Category | Plugins | |---|---| @@ -757,6 +757,7 @@ See the [full configuration reference](https://docs.fallow.tools/configuration/o | **Bundlers** | Vite, Webpack, Rspack, Rsbuild, Rollup, Rolldown, Tsup, Tsdown, pkg-utils, Parcel | | **Testing** | Vitest, Jest, Playwright, Cypress, Storybook, Stryker, Mocha, Ava, tap, tsd | | **CI/CD & Release** | Danger, Commitlint, Commitizen, Semantic Release | +| **Deployment** | Vercel, Wrangler, Sentry, OpenNext Cloudflare | | **CSS** | Tailwind, PostCSS, UnoCSS, PandaCSS | | **Databases & Backend** | Prisma, Drizzle, Knex, TypeORM, Kysely, Convex | | **Blockchain** | Hardhat | diff --git a/crates/core/src/plugins/mod.rs b/crates/core/src/plugins/mod.rs index c491d3a55..bf4aca4f2 100644 --- a/crates/core/src/plugins/mod.rs +++ b/crates/core/src/plugins/mod.rs @@ -1228,6 +1228,7 @@ mod typescript; mod unocss; mod varlock; mod velite; +mod vercel; mod vite; mod vitepress; mod vitest; diff --git a/crates/core/src/plugins/registry/builtin.rs b/crates/core/src/plugins/registry/builtin.rs index 8f688aed9..fb950ed11 100644 --- a/crates/core/src/plugins/registry/builtin.rs +++ b/crates/core/src/plugins/registry/builtin.rs @@ -34,9 +34,10 @@ use super::super::{ tailwind::TailwindPlugin, tanstack_router::TanstackRouterPlugin, tap::TapPlugin, tsd::TsdPlugin, tsdown::TsdownPlugin, tsup::TsupPlugin, turborepo::TurborepoPlugin, typedoc::TypedocPlugin, typeorm::TypeormPlugin, typescript::TypeScriptPlugin, - unocss::UnoCssPlugin, varlock::VarlockPlugin, velite::VelitePlugin, vite::VitePlugin, - vitepress::VitePressPlugin, vitest::VitestPlugin, webdriverio::WebdriverioPlugin, - webpack::WebpackPlugin, wrangler::WranglerPlugin, wuchale::WuchalePlugin, wxt::WxtPlugin, + unocss::UnoCssPlugin, varlock::VarlockPlugin, velite::VelitePlugin, vercel::VercelPlugin, + vite::VitePlugin, vitepress::VitePressPlugin, vitest::VitestPlugin, + webdriverio::WebdriverioPlugin, webpack::WebpackPlugin, wrangler::WranglerPlugin, + wuchale::WuchalePlugin, wxt::WxtPlugin, }; /// Create all built-in plugin instances, categorized by domain. @@ -145,6 +146,7 @@ pub fn create_builtin_plugins() -> Vec> { // Blockchain Box::new(HardhatPlugin), // Deployment + Box::new(VercelPlugin), Box::new(WranglerPlugin), Box::new(OpenNextCloudflarePlugin), Box::new(SentryPlugin), diff --git a/crates/core/src/plugins/vercel.rs b/crates/core/src/plugins/vercel.rs new file mode 100644 index 000000000..4f85c1b80 --- /dev/null +++ b/crates/core/src/plugins/vercel.rs @@ -0,0 +1,20 @@ +//! Vercel plugin. + +use super::{Plugin, PluginResult}; + +const ENABLERS: &[&str] = &["vercel", "@vercel/config"]; + +const CONFIG_PATTERNS: &[&str] = &["vercel.{ts,js,mjs,cjs,mts}"]; + +const ALWAYS_USED: &[&str] = &["vercel.{ts,js,mjs,cjs,mts}"]; + +const TOOLING_DEPENDENCIES: &[&str] = &["vercel", "@vercel/config"]; + +define_plugin! { + struct VercelPlugin => "vercel", + enablers: ENABLERS, + config_patterns: CONFIG_PATTERNS, + always_used: ALWAYS_USED, + tooling_dependencies: TOOLING_DEPENDENCIES, + resolve_config: imports_only, +} diff --git a/crates/core/tests/integration_test.rs b/crates/core/tests/integration_test.rs index f87af1ffb..340aaf362 100644 --- a/crates/core/tests/integration_test.rs +++ b/crates/core/tests/integration_test.rs @@ -191,6 +191,8 @@ mod issue_635_scaffold_template_assets; mod issue_638_node_script_entrypoints; #[path = "integration_test/issue_754_eslint_meta_preset.rs"] mod issue_754_eslint_meta_preset; +#[path = "integration_test/issue_820_vercel_ts_config.rs"] +mod issue_820_vercel_ts_config; #[path = "integration_test/lexical_nodes.rs"] mod lexical_nodes; #[path = "integration_test/script_multiplexers.rs"] diff --git a/crates/core/tests/integration_test/issue_820_vercel_ts_config.rs b/crates/core/tests/integration_test/issue_820_vercel_ts_config.rs new file mode 100644 index 000000000..ca58e7115 --- /dev/null +++ b/crates/core/tests/integration_test/issue_820_vercel_ts_config.rs @@ -0,0 +1,39 @@ +//! Issue #820: Vercel programmatic config files are convention entrypoints. + +use super::common::{create_config, fixture_path}; + +fn unused_file_paths( + root: &std::path::Path, + results: &fallow_types::results::AnalysisResults, +) -> Vec { + results + .unused_files + .iter() + .map(|finding| { + finding + .file + .path + .strip_prefix(root) + .unwrap_or(&finding.file.path) + .to_string_lossy() + .replace('\\', "/") + }) + .collect() +} + +#[test] +fn vercel_ts_config_is_not_reported_as_unused() { + let root = fixture_path("issue-820-vercel-ts-config"); + let config = create_config(root.clone()); + let results = fallow_core::analyze(&config).expect("analysis should succeed"); + + let unused_paths = unused_file_paths(&root, &results); + assert!( + !unused_paths.contains(&"vercel.ts".to_string()), + "vercel.ts is loaded by Vercel convention and must be credited, got {unused_paths:?}" + ); + assert!( + unused_paths.contains(&"src/orphan.ts".to_string()), + "ordinary unused file should still be reported, got {unused_paths:?}" + ); +} diff --git a/tests/fixtures/issue-820-vercel-ts-config/package.json b/tests/fixtures/issue-820-vercel-ts-config/package.json new file mode 100644 index 000000000..49c922ad9 --- /dev/null +++ b/tests/fixtures/issue-820-vercel-ts-config/package.json @@ -0,0 +1,10 @@ +{ + "name": "issue-820-vercel-ts-config", + "version": "1.0.0", + "type": "module", + "main": "src/index.ts", + "devDependencies": { + "@vercel/config": "latest", + "vercel": "latest" + } +} diff --git a/tests/fixtures/issue-820-vercel-ts-config/src/index.ts b/tests/fixtures/issue-820-vercel-ts-config/src/index.ts new file mode 100644 index 000000000..a62d878c2 --- /dev/null +++ b/tests/fixtures/issue-820-vercel-ts-config/src/index.ts @@ -0,0 +1,3 @@ +export const greeting = "hello"; + +console.log(greeting); diff --git a/tests/fixtures/issue-820-vercel-ts-config/src/orphan.ts b/tests/fixtures/issue-820-vercel-ts-config/src/orphan.ts new file mode 100644 index 000000000..bd93db0a4 --- /dev/null +++ b/tests/fixtures/issue-820-vercel-ts-config/src/orphan.ts @@ -0,0 +1 @@ +export const orphan = "unused"; diff --git a/tests/fixtures/issue-820-vercel-ts-config/vercel.ts b/tests/fixtures/issue-820-vercel-ts-config/vercel.ts new file mode 100644 index 000000000..c9860e2d3 --- /dev/null +++ b/tests/fixtures/issue-820-vercel-ts-config/vercel.ts @@ -0,0 +1,6 @@ +import type { VercelConfig } from "@vercel/config/v1"; + +export const config = { + regions: ["dub1"], + fluid: true, +} satisfies VercelConfig;