55//! config like `run` without needing a Node.js runtime.
66
77use oxc_allocator:: Allocator ;
8- use oxc_ast:: ast:: { BindingPattern , Expression , ObjectPropertyKind , Program , Statement } ;
8+ use oxc_ast:: ast:: { Expression , ObjectPropertyKind , Program , Statement } ;
99use oxc_parser:: Parser ;
1010use oxc_span:: SourceType ;
1111use rustc_hash:: FxHashMap ;
@@ -110,39 +110,19 @@ fn parse_js_ts_config(source: &str, extension: &str) -> StaticConfig {
110110 extract_config_fields ( & result. program )
111111}
112112
113- /// Scan top-level statements for a `const`/`let`/`var` declarator whose simple
114- /// binding identifier matches `name`, and return a reference to its initializer.
115- ///
116- /// Returns `None` if no match is found or the declarator has no initializer.
117- /// Destructured bindings (object/array patterns) are intentionally skipped.
118- fn find_top_level_init < ' a > ( name : & str , stmts : & ' a [ Statement < ' a > ] ) -> Option < & ' a Expression < ' a > > {
119- for stmt in stmts {
120- let Statement :: VariableDeclaration ( decl) = stmt else { continue } ;
121- for declarator in & decl. declarations {
122- let BindingPattern :: BindingIdentifier ( ident) = & declarator. id else { continue } ;
123- if ident. name == name {
124- return declarator. init . as_ref ( ) ;
125- }
126- }
127- }
128- None
129- }
130-
131113/// Find the config object in a parsed program and extract its fields.
132114///
133115/// Searches for the config value in the following patterns (in order):
134116/// 1. `export default defineConfig({ ... })`
135117/// 2. `export default { ... }`
136- /// 3. `export default config` where `config` is a top-level variable
137- /// 4. `module.exports = defineConfig({ ... })`
138- /// 5. `module.exports = { ... }`
139- /// 6. `module.exports = config` where `config` is a top-level variable
140- fn extract_config_fields < ' a > ( program : & ' a Program < ' a > ) -> StaticConfig {
118+ /// 3. `module.exports = defineConfig({ ... })`
119+ /// 4. `module.exports = { ... }`
120+ fn extract_config_fields ( program : & Program < ' _ > ) -> StaticConfig {
141121 for stmt in & program. body {
142122 // ESM: export default ...
143123 if let Statement :: ExportDefaultDeclaration ( decl) = stmt {
144124 if let Some ( expr) = decl. declaration . as_expression ( ) {
145- return extract_config_from_expr ( expr, & program . body ) ;
125+ return extract_config_from_expr ( expr) ;
146126 }
147127 // export default class/function — not analyzable
148128 return None ;
@@ -155,7 +135,7 @@ fn extract_config_fields<'a>(program: &'a Program<'a>) -> StaticConfig {
155135 m. object ( ) . is_specific_id ( "module" ) && m. static_property_name ( ) == Some ( "exports" )
156136 } )
157137 {
158- return extract_config_from_expr ( & assign. right , & program . body ) ;
138+ return extract_config_from_expr ( & assign. right ) ;
159139 }
160140 }
161141
@@ -168,12 +148,8 @@ fn extract_config_fields<'a>(program: &'a Program<'a>) -> StaticConfig {
168148/// - `defineConfig(() => { return { ... }; })` → extract from return statement
169149/// - `defineConfig(function() { return { ... }; })` → extract from return statement
170150/// - `{ ... }` → extract directly
171- /// - `identifier` → look up in `stmts`, then extract (one level of indirection only)
172151/// - anything else → not analyzable
173- fn extract_config_from_expr < ' a > (
174- expr : & ' a Expression < ' a > ,
175- stmts : & ' a [ Statement < ' a > ] ,
176- ) -> StaticConfig {
152+ fn extract_config_from_expr ( expr : & Expression < ' _ > ) -> StaticConfig {
177153 let expr = expr. without_parentheses ( ) ;
178154 match expr {
179155 Expression :: CallExpression ( call) => {
@@ -185,21 +161,15 @@ fn extract_config_from_expr<'a>(
185161 match first_arg_expr {
186162 Expression :: ObjectExpression ( obj) => Some ( extract_object_fields ( obj) ) ,
187163 Expression :: ArrowFunctionExpression ( arrow) => {
188- extract_config_from_function_body ( & arrow. body , stmts )
164+ extract_config_from_function_body ( & arrow. body )
189165 }
190166 Expression :: FunctionExpression ( func) => {
191- extract_config_from_function_body ( func. body . as_ref ( ) ?, stmts )
167+ extract_config_from_function_body ( func. body . as_ref ( ) ?)
192168 }
193169 _ => None ,
194170 }
195171 }
196172 Expression :: ObjectExpression ( obj) => Some ( extract_object_fields ( obj) ) ,
197- // Resolve a top-level identifier to its initializer (one level of indirection).
198- // Pass empty stmts on the recursive call to prevent chaining (const a = b; export default a).
199- Expression :: Identifier ( ident) if !stmts. is_empty ( ) => {
200- let init = find_top_level_init ( & ident. name , stmts) ?;
201- extract_config_from_expr ( init, & [ ] )
202- }
203173 _ => None ,
204174 }
205175}
@@ -212,13 +182,7 @@ fn extract_config_from_expr<'a>(
212182///
213183/// Returns `None` (not analyzable) if the body contains multiple `return` statements
214184/// (at any nesting depth), since the returned config would depend on runtime control flow.
215- ///
216- /// `module_stmts` is the program's top-level statement list, used as a fallback when
217- /// resolving an identifier in a `return <identifier>` statement.
218- fn extract_config_from_function_body < ' a > (
219- body : & ' a oxc_ast:: ast:: FunctionBody < ' a > ,
220- module_stmts : & ' a [ Statement < ' a > ] ,
221- ) -> StaticConfig {
185+ fn extract_config_from_function_body ( body : & oxc_ast:: ast:: FunctionBody < ' _ > ) -> StaticConfig {
222186 // Reject functions with multiple returns — the config depends on control flow.
223187 if count_returns_in_stmts ( & body. statements ) > 1 {
224188 return None ;
@@ -228,19 +192,10 @@ fn extract_config_from_function_body<'a>(
228192 match stmt {
229193 Statement :: ReturnStatement ( ret) => {
230194 let arg = ret. argument . as_ref ( ) ?;
231- match arg. without_parentheses ( ) {
232- Expression :: ObjectExpression ( obj) => return Some ( extract_object_fields ( obj) ) ,
233- Expression :: Identifier ( ident) => {
234- // Look for the binding in the function body first, then at module level.
235- let init = find_top_level_init ( & ident. name , & body. statements )
236- . or_else ( || find_top_level_init ( & ident. name , module_stmts) ) ?;
237- if let Expression :: ObjectExpression ( obj) = init. without_parentheses ( ) {
238- return Some ( extract_object_fields ( obj) ) ;
239- }
240- return None ;
241- }
242- _ => return None ,
195+ if let Expression :: ObjectExpression ( obj) = arg. without_parentheses ( ) {
196+ return Some ( extract_object_fields ( obj) ) ;
243197 }
198+ return None ;
244199 }
245200 Statement :: ExpressionStatement ( expr_stmt) => {
246201 // Concise arrow: `() => ({ ... })` is represented as ExpressionStatement
@@ -1044,125 +999,4 @@ mod tests {
1044999 ) ;
10451000 assert_json ( & result, "run" , serde_json:: json!( { "cacheScripts" : true } ) ) ;
10461001 }
1047-
1048- // ── Indirect exports (identifier resolution) ─────────────────────────
1049-
1050- #[ test]
1051- fn export_default_identifier_object ( ) {
1052- let result = parse (
1053- r"
1054- const config = { run: { cacheScripts: true } };
1055- export default config;
1056- " ,
1057- ) ;
1058- assert_json ( & result, "run" , serde_json:: json!( { "cacheScripts" : true } ) ) ;
1059- }
1060-
1061- #[ test]
1062- fn export_default_identifier_define_config ( ) {
1063- // Real-world tanstack-start-helloworld pattern
1064- let result = parse (
1065- r"
1066- import { defineConfig } from 'vite-plus';
1067- const config = defineConfig({
1068- run: { cacheScripts: true },
1069- plugins: [devtools(), nitro()],
1070- });
1071- export default config;
1072- " ,
1073- ) ;
1074- assert_json ( & result, "run" , serde_json:: json!( { "cacheScripts" : true } ) ) ;
1075- assert_non_static ( & result, "plugins" ) ;
1076- }
1077-
1078- #[ test]
1079- fn module_exports_identifier_object ( ) {
1080- let result = parse_js_ts_config (
1081- r"
1082- const config = { run: { cache: true } };
1083- module.exports = config;
1084- " ,
1085- "cjs" ,
1086- )
1087- . expect ( "expected analyzable config" ) ;
1088- assert_json ( & result, "run" , serde_json:: json!( { "cache" : true } ) ) ;
1089- }
1090-
1091- #[ test]
1092- fn module_exports_identifier_define_config ( ) {
1093- let result = parse_js_ts_config (
1094- r"
1095- const { defineConfig } = require('vite-plus');
1096- const config = defineConfig({ run: { cacheScripts: true } });
1097- module.exports = config;
1098- " ,
1099- "cjs" ,
1100- )
1101- . expect ( "expected analyzable config" ) ;
1102- assert_json ( & result, "run" , serde_json:: json!( { "cacheScripts" : true } ) ) ;
1103- }
1104-
1105- #[ test]
1106- fn define_config_callback_return_local_identifier ( ) {
1107- let result = parse (
1108- r"
1109- export default defineConfig(({ mode }) => {
1110- const obj = { run: { cacheScripts: true }, plugins: [vue()] };
1111- return obj;
1112- });
1113- " ,
1114- ) ;
1115- assert_json ( & result, "run" , serde_json:: json!( { "cacheScripts" : true } ) ) ;
1116- assert_non_static ( & result, "plugins" ) ;
1117- }
1118-
1119- #[ test]
1120- fn define_config_callback_return_module_level_identifier ( ) {
1121- let result = parse (
1122- r"
1123- const shared = { run: { cacheScripts: true } };
1124- export default defineConfig(() => {
1125- return shared;
1126- });
1127- " ,
1128- ) ;
1129- assert_json ( & result, "run" , serde_json:: json!( { "cacheScripts" : true } ) ) ;
1130- }
1131-
1132- #[ test]
1133- fn export_default_identifier_undeclared_is_none ( ) {
1134- // Identifier not declared in file — not analyzable
1135- assert ! ( parse_js_ts_config( "export default config;" , "ts" ) . is_none( ) ) ;
1136- }
1137-
1138- #[ test]
1139- fn export_default_identifier_no_init_is_none ( ) {
1140- // Variable declared without initializer — not analyzable
1141- assert ! (
1142- parse_js_ts_config(
1143- r"
1144- let config;
1145- export default config;
1146- " ,
1147- "ts" ,
1148- )
1149- . is_none( )
1150- ) ;
1151- }
1152-
1153- #[ test]
1154- fn export_default_chained_identifier_is_none ( ) {
1155- // Chained indirection (const a = b) — only one level is resolved
1156- assert ! (
1157- parse_js_ts_config(
1158- r"
1159- const inner = { run: {} };
1160- const config = inner;
1161- export default config;
1162- " ,
1163- "ts" ,
1164- )
1165- . is_none( )
1166- ) ;
1167- }
11681002}
0 commit comments