Repository: vercel/next.js
Pull request: fix(turbopack): Collect `declare global {}` in `VarDeclWithTsDeclareCollector`

### What?

Fix the issue described in https://github.com/swc-project/swc/issues/11448.

### Why?

The behavior described in the SWC issue is a kind of intended behavior. 

### How?

cc @mischnic

Changed files:
filename: turbopack/crates/turbopack-ecmascript/src/parse.rs
patch: @@ -13,7 +13,10 @@ use swc_core::{
         source_map::{Files, SourceMapGenConfig, build_source_map},
     },
     ecma::{
-        ast::{EsVersion, Id, Ident, IdentName, ObjectPatProp, Pat, Program, VarDecl},
+        ast::{
+            EsVersion, Id, Ident, IdentName, ObjectPatProp, Pat, Program, TsModuleDecl,
+            TsModuleName, VarDecl,
+        },
         lints::{self, config::LintConfig, rules::LintParams},
         parser::{EsSyntax, Parser, Syntax, TsSyntax, lexer::Lexer},
         transforms::{
@@ -735,4 +738,99 @@ impl Visit for VarDeclWithTsDeclareCollector {
             self.handle_pat(&decl.name, node.declare);
         }
     }
+
+    fn visit_ts_module_decl(&mut self, node: &TsModuleDecl) {
+        if node.declare
+            && let TsModuleName::Ident(id) = &node.id
+        {
+            self.id_with_ts_declare.insert(id.to_id());
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use swc_core::{
+        common::{FileName, GLOBALS, SourceMap, sync::Lrc},
+        ecma::parser::{Parser, Syntax, TsSyntax, lexer::Lexer},
+    };
+
+    use super::VarDeclWithTsDeclareCollector;
+
+    fn parse_and_collect(code: &str) -> Vec<String> {
+        GLOBALS.set(&Default::default(), || {
+            let cm: Lrc<SourceMap> = Default::default();
+            let fm = cm.new_source_file(FileName::Anon.into(), code.to_string());
+
+            let lexer = Lexer::new(
+                Syntax::Typescript(TsSyntax {
+                    tsx: false,
+                    decorators: true,
+                    ..Default::default()
+                }),
+                Default::default(),
+                (&*fm).into(),
+                None,
+            );
+
+            let mut parser = Parser::new_from(lexer);
+            let module = parser.parse_module().expect("Failed to parse");
+
+            let ids = VarDeclWithTsDeclareCollector::collect(&module);
+            let mut result: Vec<_> = ids.iter().map(|id| id.0.to_string()).collect();
+            result.sort();
+            result
+        })
+    }
+
+    #[test]
+    fn test_collect_declare_const() {
+        let ids = parse_and_collect("declare const Foo: number;");
+        assert_eq!(ids, vec!["Foo"]);
+    }
+
+    #[test]
+    fn test_collect_declare_global() {
+        let ids = parse_and_collect("declare global {}");
+        assert_eq!(ids, vec!["global"]);
+    }
+
+    #[test]
+    fn test_collect_declare_global_with_content() {
+        let ids = parse_and_collect(
+            r#"
+            declare global {
+                interface Window {
+                    foo: string;
+                }
+            }
+            "#,
+        );
+        assert_eq!(ids, vec!["global"]);
+    }
+
+    #[test]
+    fn test_collect_multiple_declares() {
+        let ids = parse_and_collect(
+            r#"
+            declare const Foo: number;
+            declare global {}
+            declare const Bar: string;
+            "#,
+        );
+        assert_eq!(ids, vec!["Bar", "Foo", "global"]);
+    }
+
+    #[test]
+    fn test_no_collect_non_declare() {
+        let ids = parse_and_collect("const Foo = 1;");
+        assert!(ids.is_empty());
+    }
+
+    #[test]
+    fn test_collect_declare_namespace() {
+        // `declare namespace Foo {}` should also be collected
+        let ids = parse_and_collect("declare namespace Foo {}");
+        assert_eq!(ids, vec!["Foo"]);
+    }
 }

filename: turbopack/crates/turbopack-tests/tests/execution/turbopack/code-gen/global-rewrite-declare/input/foo.ts
patch: @@ -0,0 +1,7 @@
+declare global {
+  var something: number
+}
+
+export function foo() {
+  return global.something
+}

filename: turbopack/crates/turbopack-tests/tests/execution/turbopack/code-gen/global-rewrite-declare/input/index.js
patch: @@ -0,0 +1,11 @@
+import { foo } from './foo'
+
+it('should rewrite global', () => {
+  // Workaround because the tests run in a Node.js environment where `global` is always defined
+  delete globalThis.global
+
+  globalThis.something = 1234
+  expect(foo()).toBe(1234)
+
+  globalThis.global = globalThis
+})