@@ -49,6 +49,12 @@ const CONFIG_FILE_NAMES: &[&str] = &[
4949 "vite.config.cts" ,
5050] ;
5151
52+ /// Check whether any vite config file exists in the given directory.
53+ #[ must_use]
54+ pub fn has_config_file ( dir : & AbsolutePath ) -> bool {
55+ resolve_config_path ( dir) . is_some ( )
56+ }
57+
5258/// Resolve the vite config file path in the given directory.
5359///
5460/// Tries each config file name in priority order and returns the first one that exists.
@@ -139,6 +145,9 @@ fn extract_config_fields(program: &Program<'_>) -> StaticConfig {
139145
140146/// Extract the config object from an expression that is either:
141147/// - `defineConfig({ ... })` → extract the object argument
148+ /// - `defineConfig(() => ({ ... }))` → extract from arrow function expression body
149+ /// - `defineConfig(() => { return { ... }; })` → extract from return statement
150+ /// - `defineConfig(function() { return { ... }; })` → extract from return statement
142151/// - `{ ... }` → extract directly
143152/// - anything else → not analyzable
144153fn extract_config_from_expr ( expr : & Expression < ' _ > ) -> StaticConfig {
@@ -148,18 +157,54 @@ fn extract_config_from_expr(expr: &Expression<'_>) -> StaticConfig {
148157 if !call. callee . is_specific_id ( "defineConfig" ) {
149158 return None ;
150159 }
151- if let Some ( first_arg) = call. arguments . first ( )
152- && let Some ( Expression :: ObjectExpression ( obj) ) = first_arg. as_expression ( )
153- {
154- return Some ( extract_object_fields ( obj) ) ;
160+ let first_arg = call. arguments . first ( ) ?;
161+ let first_arg_expr = first_arg. as_expression ( ) ?;
162+ match first_arg_expr {
163+ Expression :: ObjectExpression ( obj) => Some ( extract_object_fields ( obj) ) ,
164+ Expression :: ArrowFunctionExpression ( arrow) => {
165+ extract_config_from_function_body ( & arrow. body )
166+ }
167+ Expression :: FunctionExpression ( func) => {
168+ extract_config_from_function_body ( func. body . as_ref ( ) ?)
169+ }
170+ _ => None ,
155171 }
156- None
157172 }
158173 Expression :: ObjectExpression ( obj) => Some ( extract_object_fields ( obj) ) ,
159174 _ => None ,
160175 }
161176}
162177
178+ /// Extract the config object from the body of a function passed to `defineConfig`.
179+ ///
180+ /// Handles two patterns:
181+ /// - Concise arrow body: `() => ({ ... })` — body has a single `ExpressionStatement`
182+ /// - Block body: `() => { ... return { ... }; }` — find top-level `ReturnStatement`
183+ fn extract_config_from_function_body ( body : & oxc_ast:: ast:: FunctionBody < ' _ > ) -> StaticConfig {
184+ for stmt in & body. statements {
185+ match stmt {
186+ Statement :: ReturnStatement ( ret) => {
187+ if let Some ( arg) = & ret. argument {
188+ if let Expression :: ObjectExpression ( obj) = arg. without_parentheses ( ) {
189+ return Some ( extract_object_fields ( obj) ) ;
190+ }
191+ }
192+ return None ;
193+ }
194+ Statement :: ExpressionStatement ( expr_stmt) => {
195+ // Concise arrow: `() => ({ ... })` is represented as ExpressionStatement
196+ if let Expression :: ObjectExpression ( obj) =
197+ expr_stmt. expression . without_parentheses ( )
198+ {
199+ return Some ( extract_object_fields ( obj) ) ;
200+ }
201+ }
202+ _ => { }
203+ }
204+ }
205+ None
206+ }
207+
163208/// Extract fields from an object expression, converting each value to JSON.
164209/// Fields whose values cannot be represented as pure JSON are recorded as
165210/// [`FieldValue::NonStatic`]. Spread elements and computed properties
@@ -720,6 +765,78 @@ mod tests {
720765 assert_json ( & result, "b" , serde_json:: json!( 2 ) ) ;
721766 }
722767
768+ // ── defineConfig with function argument ────────────────────────────
769+
770+ #[ test]
771+ fn define_config_arrow_block_body ( ) {
772+ let result = parse (
773+ r"
774+ export default defineConfig(({ mode }) => {
775+ const env = loadEnv(mode, process.cwd(), '');
776+ return {
777+ run: { cacheScripts: true },
778+ plugins: [vue()],
779+ };
780+ });
781+ " ,
782+ ) ;
783+ assert_json ( & result, "run" , serde_json:: json!( { "cacheScripts" : true } ) ) ;
784+ assert_non_static ( & result, "plugins" ) ;
785+ }
786+
787+ #[ test]
788+ fn define_config_arrow_expression_body ( ) {
789+ let result = parse (
790+ r"
791+ export default defineConfig(() => ({
792+ run: { cacheScripts: true },
793+ build: { outDir: 'dist' },
794+ }));
795+ " ,
796+ ) ;
797+ assert_json ( & result, "run" , serde_json:: json!( { "cacheScripts" : true } ) ) ;
798+ assert_json ( & result, "build" , serde_json:: json!( { "outDir" : "dist" } ) ) ;
799+ }
800+
801+ #[ test]
802+ fn define_config_function_expression ( ) {
803+ let result = parse (
804+ r"
805+ export default defineConfig(function() {
806+ return {
807+ run: { cacheScripts: true },
808+ plugins: [react()],
809+ };
810+ });
811+ " ,
812+ ) ;
813+ assert_json ( & result, "run" , serde_json:: json!( { "cacheScripts" : true } ) ) ;
814+ assert_non_static ( & result, "plugins" ) ;
815+ }
816+
817+ #[ test]
818+ fn define_config_arrow_no_return_object ( ) {
819+ // Arrow function that doesn't return an object literal
820+ assert ! ( parse_js_ts_config(
821+ r"
822+ export default defineConfig(({ mode }) => {
823+ return someFunction();
824+ });
825+ " ,
826+ "ts" ,
827+ )
828+ . is_none( ) ) ;
829+ }
830+
831+ #[ test]
832+ fn define_config_arrow_empty_body ( ) {
833+ assert ! ( parse_js_ts_config(
834+ "export default defineConfig(() => {});" ,
835+ "ts" ,
836+ )
837+ . is_none( ) ) ;
838+ }
839+
723840 // ── Not analyzable cases (return None) ──────────────────────────────
724841
725842 #[ test]
0 commit comments