Repository: swc-project/swc (rust)

Issue:
Add support for *.mts/*.cts files
### Describe the feature

The current default supportet file extensions by the swc-cli are:
```["js", "jsx", "es6", "es", "mjs", "ts", "tsx"]```
I would love having support for `.mts` and `.cts` files which work like `.ts` or `.tsx` files
`.mts` => `.mjs`
`.cts` => `.cjs`
just like with `tsx` and `jsx`
`.tsx` => `.jsx`

it would work just like `ts` files but with an other extension (or optional even change the module settings)

### Babel plugin or link to the feature description

_No response_

### Additional context

https://www.typescriptlang.org/docs/handbook/esm-node.html#new-file-extensions

Pull request:
feat(cli): Support for *.mts/*.cts files
<!-- Note: CI script will automatically rebase your PR so please do not rebase unless required -->

<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**Description:** Fix #4679

<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->

**BREAKING CHANGE:**

<!--
If this PR introduces a breaking change, it must contain a notice for it to be included in the CHANGELOG. Add description or remove entirely if not breaking.

You may need to update `MIGRATION.md` for the breaking changes.
-->

**Related issue (if exists):** Fix #4679 I also referred to https://github.com/babel/babel/pull/13838



Patch:
diff --git a/bindings/swc_cli/src/commands/compile.rs b/bindings/swc_cli/src/commands/compile.rs
index b53d450090..4cef8539c2 100644
--- a/bindings/swc_cli/src/commands/compile.rs
+++ b/bindings/swc_cli/src/commands/compile.rs
@@ -121,7 +121,7 @@ static COMPILER: Lazy<Arc<Compiler>> = Lazy::new(|| {
 });
 
 /// List of file extensions supported by default.
-static DEFAULT_EXTENSIONS: &[&str] = &["js", "jsx", "es6", "es", "mjs", "ts", "tsx"];
+static DEFAULT_EXTENSIONS: &[&str] = &["js", "jsx", "es6", "es", "mjs", "ts", "tsx", "cts", "mts"];
 
 /// Infer list of files to be transformed from cli arguments.
 /// If given input is a directory, it'll traverse it and collect all supported
diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs
index 78ee3fda6b..28610e524b 100644
--- a/crates/swc/src/config/mod.rs
+++ b/crates/swc/src/config/mod.rs
@@ -768,6 +768,19 @@ impl Default for Rc {
                 },
                 ..Default::default()
             },
+            Config {
+                env: None,
+                test: Some(FileMatcher::Regex("\\.(cts|mts)$".into())),
+                exclude: None,
+                jsc: JscConfig {
+                    syntax: Some(Syntax::Typescript(TsConfig {
+                        tsx: false,
+                        ..Default::default()
+                    })),
+                    ..Default::default()
+                },
+                ..Default::default()
+            },
             Config {
                 env: None,
                 test: Some(FileMatcher::Regex("\\.ts$".into())),


Test patch:
diff --git a/crates/swc_ecma_parser/tests/typescript.rs b/crates/swc_ecma_parser/tests/typescript.rs
index 661a8c7241..bbade2a633 100644
--- a/crates/swc_ecma_parser/tests/typescript.rs
+++ b/crates/swc_ecma_parser/tests/typescript.rs
@@ -67,6 +67,8 @@ fn shifted(file: PathBuf) {
 }
 
 #[testing::fixture("tests/typescript/**/*.ts")]
+#[testing::fixture("tests/typescript/**/*.mts")]
+#[testing::fixture("tests/typescript/**/*.cts")]
 #[testing::fixture("tests/typescript/**/*.tsx")]
 fn spec(file: PathBuf) {
     let output = file.parent().unwrap().join(format!(
diff --git a/crates/swc_ecma_parser/tests/typescript/cts/1.cts b/crates/swc_ecma_parser/tests/typescript/cts/1.cts
new file mode 100644
index 0000000000..51366249c4
--- /dev/null
+++ b/crates/swc_ecma_parser/tests/typescript/cts/1.cts
@@ -0,0 +1,3 @@
+const path = require('path');
+
+console.log("hello, world!");
diff --git a/crates/swc_ecma_parser/tests/typescript/cts/1.cts.json b/crates/swc_ecma_parser/tests/typescript/cts/1.cts.json
new file mode 100644
index 0000000000..80721b2be9
--- /dev/null
+++ b/crates/swc_ecma_parser/tests/typescript/cts/1.cts.json
@@ -0,0 +1,137 @@
+{
+  "type": "Script",
+  "span": {
+    "start": 1,
+    "end": 61,
+    "ctxt": 0
+  },
+  "body": [
+    {
+      "type": "VariableDeclaration",
+      "span": {
+        "start": 1,
+        "end": 30,
+        "ctxt": 0
+      },
+      "kind": "const",
+      "declare": false,
+      "declarations": [
+        {
+          "type": "VariableDeclarator",
+          "span": {
+            "start": 7,
+            "end": 29,
+            "ctxt": 0
+          },
+          "id": {
+            "type": "Identifier",
+            "span": {
+              "start": 7,
+              "end": 11,
+              "ctxt": 0
+            },
+            "value": "path",
+            "optional": false,
+            "typeAnnotation": null
+          },
+          "init": {
+            "type": "CallExpression",
+            "span": {
+              "start": 14,
+              "end": 29,
+              "ctxt": 0
+            },
+            "callee": {
+              "type": "Identifier",
+              "span": {
+                "start": 14,
+                "end": 21,
+                "ctxt": 0
+              },
+              "value": "require",
+              "optional": false
+            },
+            "arguments": [
+              {
+                "spread": null,
+                "expression": {
+                  "type": "StringLiteral",
+                  "span": {
+                    "start": 22,
+                    "end": 28,
+                    "ctxt": 0
+                  },
+                  "value": "path",
+                  "raw": "'path'"
+                }
+              }
+            ],
+            "typeArguments": null
+          },
+          "definite": false
+        }
+      ]
+    },
+    {
+      "type": "ExpressionStatement",
+      "span": {
+        "start": 32,
+        "end": 61,
+        "ctxt": 0
+      },
+      "expression": {
+        "type": "CallExpression",
+        "span": {
+          "start": 32,
+          "end": 60,
+          "ctxt": 0
+        },
+        "callee": {
+          "type": "MemberExpression",
+          "span": {
+            "start": 32,
+            "end": 43,
+            "ctxt": 0
+          },
+          "object": {
+            "type": "Identifier",
+            "span": {
+              "start": 32,
+              "end": 39,
+              "ctxt": 0
+            },
+            "value": "console",
+            "optional": false
+          },
+          "property": {
+            "type": "Identifier",
+            "span": {
+              "start": 40,
+              "end": 43,
+              "ctxt": 0
+            },
+            "value": "log",
+            "optional": false
+          }
+        },
+        "arguments": [
+          {
+            "spread": null,
+            "expression": {
+              "type": "StringLiteral",
+              "span": {
+                "start": 44,
+                "end": 59,
+                "ctxt": 0
+              },
+              "value": "hello, world!",
+              "raw": "\"hello, world!\""
+            }
+          }
+        ],
+        "typeArguments": null
+      }
+    }
+  ],
+  "interpreter": null
+}
diff --git a/crates/swc_ecma_parser/tests/typescript/mts/1.mts b/crates/swc_ecma_parser/tests/typescript/mts/1.mts
new file mode 100644
index 0000000000..84673ffd43
--- /dev/null
+++ b/crates/swc_ecma_parser/tests/typescript/mts/1.mts
@@ -0,0 +1,3 @@
+import path from "node:path";
+
+console.log("hello, world!");
diff --git a/crates/swc_ecma_parser/tests/typescript/mts/1.mts.json b/crates/swc_ecma_parser/tests/typescript/mts/1.mts.json
new file mode 100644
index 0000000000..0308fe47ac
--- /dev/null
+++ b/crates/swc_ecma_parser/tests/typescript/mts/1.mts.json
@@ -0,0 +1,111 @@
+{
+  "type": "Module",
+  "span": {
+    "start": 1,
+    "end": 61,
+    "ctxt": 0
+  },
+  "body": [
+    {
+      "type": "ImportDeclaration",
+      "span": {
+        "start": 1,
+        "end": 30,
+        "ctxt": 0
+      },
+      "specifiers": [
+        {
+          "type": "ImportDefaultSpecifier",
+          "span": {
+            "start": 8,
+            "end": 12,
+            "ctxt": 0
+          },
+          "local": {
+            "type": "Identifier",
+            "span": {
+              "start": 8,
+              "end": 12,
+              "ctxt": 0
+            },
+            "value": "path",
+            "optional": false
+          }
+        }
+      ],
+      "source": {
+        "type": "StringLiteral",
+        "span": {
+          "start": 18,
+          "end": 29,
+          "ctxt": 0
+        },
+        "value": "node:path",
+        "raw": "\"node:path\""
+      },
+      "typeOnly": false,
+      "asserts": null
+    },
+    {
+      "type": "ExpressionStatement",
+      "span": {
+        "start": 32,
+        "end": 61,
+        "ctxt": 0
+      },
+      "expression": {
+        "type": "CallExpression",
+        "span": {
+          "start": 32,
+          "end": 60,
+          "ctxt": 0
+        },
+        "callee": {
+          "type": "MemberExpression",
+          "span": {
+            "start": 32,
+            "end": 43,
+            "ctxt": 0
+          },
+          "object": {
+            "type": "Identifier",
+            "span": {
+              "start": 32,
+              "end": 39,
+              "ctxt": 0
+            },
+            "value": "console",
+            "optional": false
+          },
+          "property": {
+            "type": "Identifier",
+            "span": {
+              "start": 40,
+              "end": 43,
+              "ctxt": 0
+            },
+            "value": "log",
+            "optional": false
+          }
+        },
+        "arguments": [
+          {
+            "spread": null,
+            "expression": {
+              "type": "StringLiteral",
+              "span": {
+                "start": 44,
+                "end": 59,
+                "ctxt": 0
+              },
+              "value": "hello, world!",
+              "raw": "\"hello, world!\""
+            }
+          }
+        ],
+        "typeArguments": null
+      }
+    }
+  ],
+  "interpreter": null
+}