Repository: swc-project/swc (rust)

Issue:
`const` in ambient context incorrectly flagged as uninitialised
### Describe the bug

Since #3742 was introduced in version 1.2.46 (issue: #3730), not only uninitialised. const declarations in 'strict' mode/ real code get flagged with `error: 'const' declarations must be initialized` but also those in ambient context (type declaration only/ .d.ts). However in ambient contexts a `const` _cannot_ be initialised; `tsc` will complain with a TS1039: `Initializers are not allowed in ambient contexts.ts(1039)`


### Input code

```typescript
// filename: types.d.ts
export const aThing: IAnInterface[];

export interface IAnInterface {
  anAttribute: string;
  anotherAttribute: boolean;
}
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false,
      "decorators": true
    },
    "target": "es2022",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "es6",
    "strict": false,
    "strictMode": false
  },
  "minify": false,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.2.149&code=H4sIAAAAAAAAA1WMMQqEMBBF%2BznFnEB7U1nab7dYRHeigTAjyRd2Wby7Igjafd5%2FvLrmEJM0jN8ipfpUKCTfxTJ4NC1g%2F5qjTg13rXYKycGP8u4dXVa84MPgPzF7bYEchxVHvxxLJ3diwyz59g1mSbw62mgHJbEkGZAAAAA%3D&config=H4sIAAAAAAAAA0WOSw6DMAwF7%2BI1iyqLLrgDh4iCQakAR35GKoq4e0OgsPNvxi%2FTB4HaTMkrWI8K22L%2BSy3ZlhhBYzJqyFBGg5%2FADfUcRL2JglrTlfey9jqyFYjhXs4VYBIB38gclzhshz7InJSBZ%2BWXcfpf7sU1S78eg1wTVOe7CGEag93Y2XbS3%2Bjz5TqJ6C5VTfkDjgOVHewAAAA%3D

### Expected behavior

In ambient contexts (type declaration files) uninitialised `const` declarations are allowed.

### Actual behavior

error: 'const' declarations must be initialized

### Version

@swc/core 1.2.149

### Additional context

- I've tried to play around with _parser_ options (e.g. to indicate the input is not _strict_) but failed to do so. If there's an option to influence this behavior, I'll be all ears.
- I encountered this issue while using @swc/core for static analysis (and not with the swc cli per se). Sample snippet:

```javascript
const SWC_PARSE_OPTIONS = {
  dynamicImport: true,
  syntax: "typescript",
  target: "es2022",
};
console.dir(
  swc.parseFileSync('types.d.ts', SWC_PARSE_OPTIONS), 
  {depth:10}
)

// no output, but instead on stderr:
/* 
Uncaught Error: error: 'const' declarations must be initialized
  --> types.d.ts:2:14
   |
14 | export const aThing: IAnInterface[];
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^



Caused by:
    0: error was recoverable, but proceeding would result in wrong codegen
    1: Syntax Error
    at Compiler.parseFileSync (/Users/sander/thing/node_modules/@swc/core/index.js:87:36)
    at Object.parseFileSync (/Users/sander/thing/node_modules/@swc/core/index.js:201:21) {
  code: 'GenericFailure'
*/
```

Pull request:
fix(es/parser): Fix parsing of `const` in ambient context
**Description:**



**Related issue (if exists):**

 - Closes #3882


Patch:
diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs
index b84f0a0820..50b30cae00 100644
--- a/crates/swc/src/config/mod.rs
+++ b/crates/swc/src/config/mod.rs
@@ -887,7 +887,17 @@ impl Config {
     ///
     /// - typescript: `tsx` will be modified if file extension is `ts`.
     pub fn adjust(&mut self, file: &Path) {
-        if let Some(Syntax::Typescript(TsConfig { tsx, .. })) = &mut self.jsc.syntax {
+        if let Some(Syntax::Typescript(TsConfig { tsx, dts, .. })) = &mut self.jsc.syntax {
+            let is_dts = file
+                .file_name()
+                .and_then(|f| f.to_str())
+                .map(|s| s.ends_with(".d.ts"))
+                .unwrap_or(false);
+
+            if is_dts {
+                *dts = true;
+            }
+
             let is_ts = file.extension().map(|v| v == "ts").unwrap_or(false);
             if is_ts {
                 *tsx = false;
diff --git a/crates/swc_ecma_parser/src/parser/mod.rs b/crates/swc_ecma_parser/src/parser/mod.rs
index 38e94a6304..fa38e77e5a 100644
--- a/crates/swc_ecma_parser/src/parser/mod.rs
+++ b/crates/swc_ecma_parser/src/parser/mod.rs
@@ -11,7 +11,7 @@ use crate::{
     error::SyntaxError,
     lexer::Lexer,
     token::{Token, Word},
-    Context, EsVersion, Syntax,
+    Context, EsVersion, Syntax, TsConfig,
 };
 #[cfg(test)]
 extern crate test;
@@ -61,7 +61,17 @@ impl<'a, I: Input> Parser<Lexer<'a, I>> {
 }
 
 impl<I: Tokens> Parser<I> {
-    pub fn new_from(input: I) -> Self {
+    pub fn new_from(mut input: I) -> Self {
+        let in_declare = matches!(
+            input.syntax(),
+            Syntax::Typescript(TsConfig { dts: true, .. })
+        );
+        let ctx = Context {
+            in_declare,
+            ..input.ctx()
+        };
+        input.set_ctx(ctx);
+
         Parser {
             state: Default::default(),
             input: Buffer::new(input),
diff --git a/crates/swc_ecma_parser/src/parser/stmt.rs b/crates/swc_ecma_parser/src/parser/stmt.rs
index 2a0c6e1f9a..ce3e98af03 100644
--- a/crates/swc_ecma_parser/src/parser/stmt.rs
+++ b/crates/swc_ecma_parser/src/parser/stmt.rs
@@ -809,7 +809,8 @@ impl<'a, I: Tokens> Parser<I> {
                 // typescript allows `declare` vars not to have initializers.
                 if self.ctx().in_declare {
                     None
-                } else if kind == VarDeclKind::Const && self.ctx().strict {
+                } else if kind == VarDeclKind::Const && self.ctx().strict && !self.ctx().in_declare
+                {
                     self.emit_err(
                         span!(self, start),
                         SyntaxError::ConstDeclarationsRequireInitialization,


Test patch:
diff --git a/crates/swc/tests/fixture/issue-3882/input/.swcrc b/crates/swc/tests/fixture/issue-3882/input/.swcrc
new file mode 100644
index 0000000000..2a0adb34a3
--- /dev/null
+++ b/crates/swc/tests/fixture/issue-3882/input/.swcrc
@@ -0,0 +1,21 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "typescript",
+            "tsx": false,
+            "decorators": true
+        },
+        "target": "es2022",
+        "loose": false,
+        "minify": {
+            "compress": false,
+            "mangle": false
+        }
+    },
+    "module": {
+        "type": "es6",
+        "strict": false,
+        "strictMode": false
+    },
+    "minify": false
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issue-3882/input/types.d.ts b/crates/swc/tests/fixture/issue-3882/input/types.d.ts
new file mode 100644
index 0000000000..e15dee0ac0
--- /dev/null
+++ b/crates/swc/tests/fixture/issue-3882/input/types.d.ts
@@ -0,0 +1,7 @@
+// filename: types.d.ts
+export const aThing: IAnInterface[];
+
+export interface IAnInterface {
+    anAttribute: string;
+    anotherAttribute: boolean;
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issue-3882/output/types.d.ts b/crates/swc/tests/fixture/issue-3882/output/types.d.ts
new file mode 100644
index 0000000000..cf4560479c
--- /dev/null
+++ b/crates/swc/tests/fixture/issue-3882/output/types.d.ts
@@ -0,0 +1,1 @@
+export const aThing;