From 16f7afac4862350bb39c28620fe05323cb70db88 Mon Sep 17 00:00:00 2001 From: Vivek Chavan <111511821+vivekchavan14@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:21:57 +0530 Subject: [PATCH] fix: detect preprocessor in re-exported named exports (#6899) Fixes #6894 The TypeScript parser now correctly detects preprocessor functions that are re-exported from other modules using named exports like: export { preprocessor } from "./other_module"; Previously, only function declarations were detected. Now the parser also checks ExportNamed AST nodes for any specifier named 'preprocessor'. This allows developers to easily reuse preprocessor functions across multiple scripts without the workaround of wrapping them in a new function. Added comprehensive tests covering: - Simple re-export: export { preprocessor } from "./other" - Re-export with renaming: export { preprocessor as preprocessor } - Mixed exports: export { foo, preprocessor, bar } - Negative case: exports without preprocessor --- backend/parsers/windmill-parser-ts/src/lib.rs | 22 ++++++ .../parsers/windmill-parser-ts/tests/tests.rs | 72 +++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/backend/parsers/windmill-parser-ts/src/lib.rs b/backend/parsers/windmill-parser-ts/src/lib.rs index 399e13aa0e..04dd345b2f 100644 --- a/backend/parsers/windmill-parser-ts/src/lib.rs +++ b/backend/parsers/windmill-parser-ts/src/lib.rs @@ -255,6 +255,28 @@ pub fn parse_deno_signature( let mut symbol_table: HashMap = HashMap::new(); for item in ast { + // Check for named exports (e.g., export { preprocessor } from "./other") + if let ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named_export)) = &item { + if !has_preprocessor { + for specifier in &named_export.specifiers { + if let swc_ecma_ast::ExportSpecifier::Named(spec) = specifier { + let export_name = match &spec.exported { + Some(swc_ecma_ast::ModuleExportName::Ident(ident)) => ident.sym.as_ref(), + Some(swc_ecma_ast::ModuleExportName::Str(s)) => s.value.as_ref(), + None => match &spec.orig { + swc_ecma_ast::ModuleExportName::Ident(ident) => ident.sym.as_ref(), + swc_ecma_ast::ModuleExportName::Str(s) => s.value.as_ref(), + }, + }; + if export_name == "preprocessor" { + has_preprocessor = true; + break; + } + } + } + } + } + if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, .. })) | ModuleItem::Stmt(Stmt::Decl(decl)) = item { diff --git a/backend/parsers/windmill-parser-ts/tests/tests.rs b/backend/parsers/windmill-parser-ts/tests/tests.rs index f5a3404df8..1d9fbda6ef 100644 --- a/backend/parsers/windmill-parser-ts/tests/tests.rs +++ b/backend/parsers/windmill-parser-ts/tests/tests.rs @@ -685,4 +685,76 @@ mod tests { } ); } + + #[test] + fn test_parse_with_preprocessor_reexport() { + // Test case for issue #6894: preprocessor re-export should be detected + let code = r#" + export { preprocessor } from "./extract_user_info_from_jwt_token"; + + export async function main(param: string) { + return param; + } + "#; + let sig = parse_deno_signature(code, false, false, None).unwrap(); + assert_eq!( + sig, + MainArgSignature { + star_args: false, + star_kwargs: false, + args: vec![Arg { + name: "param".to_string(), + otyp: None, + typ: Typ::Str(None), + default: None, + has_default: false, + oidx: None, + }], + no_main_func: Some(false), + has_preprocessor: Some(true), + } + ); + } + + #[test] + fn test_parse_with_preprocessor_reexport_renamed() { + // Test case for renamed re-exports + let code = r#" + export { preprocessor as preprocessor } from "./other"; + + export async function main(param: string) { + return param; + } + "#; + let sig = parse_deno_signature(code, false, false, None).unwrap(); + assert_eq!(sig.has_preprocessor, Some(true)); + } + + #[test] + fn test_parse_with_preprocessor_among_other_exports() { + // Test case where preprocessor is one of many exports + let code = r#" + export { foo, preprocessor, bar } from "./utils"; + + export async function main(param: string) { + return param; + } + "#; + let sig = parse_deno_signature(code, false, false, None).unwrap(); + assert_eq!(sig.has_preprocessor, Some(true)); + } + + #[test] + fn test_parse_without_preprocessor_other_exports() { + // Test case where there are exports but no preprocessor + let code = r#" + export { foo, bar } from "./utils"; + + export async function main(param: string) { + return param; + } + "#; + let sig = parse_deno_signature(code, false, false, None).unwrap(); + assert_eq!(sig.has_preprocessor, Some(false)); + } }