Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,16 @@ impl<'a> Traverse<'a, TransformState<'a>> for TransformerImpl<'a> {
}
}

fn enter_import_expression(
&mut self,
node: &mut ImportExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if let Some(typescript) = self.x0_typescript.as_mut() {
typescript.enter_import_expression(node, ctx);
}
}

fn enter_export_all_declaration(
&mut self,
node: &mut ExportAllDeclaration<'a>,
Expand Down
10 changes: 10 additions & 0 deletions crates/oxc_transformer/src/typescript/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,16 @@ impl<'a> Traverse<'a, TransformState<'a>> for TypeScript<'a> {
}
}

fn enter_import_expression(
&mut self,
node: &mut ImportExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
if let Some(rewrite_extensions) = &mut self.rewrite_extensions {
rewrite_extensions.enter_import_expression(node, ctx);
}
}

fn enter_formal_parameter_rest(
&mut self,
node: &mut FormalParameterRest<'a>,
Expand Down
85 changes: 66 additions & 19 deletions crates/oxc_transformer/src/typescript/rewrite_extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
//! Based on Babel's [plugin-rewrite-ts-imports](https://github.com/babel/babel/blob/3bcfee232506a4cebe410f02042fb0f0adeeb0b1/packages/babel-preset-typescript/src/plugin-rewrite-ts-imports.ts)

use oxc_ast::ast::{
ExportAllDeclaration, ExportNamedDeclaration, ImportDeclaration, StringLiteral,
ExportAllDeclaration, ExportNamedDeclaration, Expression, ImportDeclaration, ImportExpression,
StringLiteral, TemplateLiteral,
};
use oxc_span::Atom;
use oxc_traverse::Traverse;
Expand All @@ -19,32 +20,62 @@ pub struct TypeScriptRewriteExtensions {
mode: RewriteExtensionsMode,
}

/// Given a specifier value, compute the replacement atom if the extension
/// should be rewritten/removed. Returns `None` when no rewriting is needed.
fn rewritten_specifier<'a>(
value: &'a str,
mode: RewriteExtensionsMode,
ctx: &TraverseCtx<'a>,
) -> Option<Atom<'a>> {
if !value.contains(['/', '\\']) {
return None;
}

let (without_extension, extension) = value.rsplit_once('.')?;

let replace = match extension {
"mts" => ".mjs",
"cts" => ".cjs",
"ts" | "tsx" => ".js",
_ => return None,
};

Some(if mode.is_remove() {
Atom::from(without_extension)
} else {
ctx.ast.atom_from_strs_array([without_extension, replace])
})
}

impl TypeScriptRewriteExtensions {
pub fn new(options: &TypeScriptOptions) -> Option<Self> {
options.rewrite_import_extensions.map(|mode| Self { mode })
}

pub fn rewrite_extensions<'a>(&self, source: &mut StringLiteral<'a>, ctx: &TraverseCtx<'a>) {
let value = source.value.as_str();
if !value.contains(['/', '\\']) {
return;
if let Some(rewritten) = rewritten_specifier(source.value.as_str(), self.mode, ctx) {
source.value = rewritten;
source.raw = None;
}
}

let Some((without_extension, extension)) = value.rsplit_once('.') else { return };

let replace = match extension {
"mts" => ".mjs",
"cts" => ".cjs",
"ts" | "tsx" => ".js",
_ => return, // do not rewrite or remove other unknown extensions
};

source.value = if self.mode.is_remove() {
Atom::from(without_extension)
} else {
ctx.ast.atom_from_strs_array([without_extension, replace])
};
source.raw = None;
fn rewrite_template_literal<'a>(
&self,
template: &mut TemplateLiteral<'a>,
ctx: &TraverseCtx<'a>,
) {
if !template.is_no_substitution_template() {
return;
}
let quasi = &mut template.quasis[0];
// Read the specifier value from raw (always present).
// For no-substitution templates, raw and cooked are identical
// unless the template contains escape sequences, which import
// specifiers never do.
if let Some(rewritten) = rewritten_specifier(quasi.value.raw.as_str(), self.mode, ctx) {
quasi.value.raw = rewritten;
quasi.value.cooked = Some(rewritten);
}
}
}

Expand Down Expand Up @@ -83,4 +114,20 @@ impl<'a> Traverse<'a, TransformState<'a>> for TypeScriptRewriteExtensions {
}
self.rewrite_extensions(&mut node.source, ctx);
}

fn enter_import_expression(
&mut self,
node: &mut ImportExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) {
match &mut node.source {
Expression::StringLiteral(source) => {
self.rewrite_extensions(source, ctx);
}
Expression::TemplateLiteral(template) => {
self.rewrite_template_literal(template, ctx);
}
_ => {}
}
}
}
2 changes: 1 addition & 1 deletion tasks/transform_conformance/snapshots/oxc.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
commit: de54b9b2

Passed: 203/334
Passed: 204/335

# All Passed:
* babel-plugin-transform-class-static-block
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,15 @@ import "a-package/file.ts";
// Bare import, it's either a node package or remapped by an import map
import "soundcloud.ts";
import "ipaddr.js";
// Dynamic imports should also be rewritten.
import("./a.ts");
import("./a.mts");
import("./a.cts");
import("./react.tsx");
import("a-package/file.ts");
// Bare dynamic import should not be rewritten.
import("soundcloud.ts");
// No-substitution template literal should also be rewritten.
import(`./a.ts`);
// Non-string-literal dynamic import should not be rewritten.
import(dynamicPath);
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,15 @@ import "a-package/file";
// Bare import, it's either a node package or remapped by an import map
import "soundcloud.ts";
import "ipaddr.js";
// Dynamic imports should also be rewritten.
import("./a");
import("./a");
import("./a");
import("./react");
import("a-package/file");
// Bare dynamic import should not be rewritten.
import("soundcloud.ts");
// No-substitution template literal should also be rewritten.
import(`./a`);
// Non-string-literal dynamic import should not be rewritten.
import(dynamicPath);
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import "./a.ts";
import "./a.mts";
import "./a.cts";
import "./react.tsx";
// .mtsx and .ctsx are not valid and should not be transformed.
import "./react.mtsx";
import "./react.ctsx";
import "a-package/file.ts";
// Bare import, it's either a node package or remapped by an import map
import "soundcloud.ts";
import "ipaddr.js";
// Dynamic imports should also be rewritten.
import("./a.ts");
import("./a.mts");
import("./a.cts");
import("./react.tsx");
import("a-package/file.ts");
// Bare dynamic import should not be rewritten.
import("soundcloud.ts");
// No-substitution template literal should also be rewritten.
import(`./a.ts`);
// Non-string-literal dynamic import should not be rewritten.
import(dynamicPath);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"sourceType": "module",
"presets": [["typescript", { "rewriteImportExtensions": "rewrite" }]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import "./a.js";
import "./a.mjs";
import "./a.cjs";
import "./react.js";
// .mtsx and .ctsx are not valid and should not be transformed.
import "./react.mtsx";
import "./react.ctsx";
import "a-package/file.js";
// Bare import, it's either a node package or remapped by an import map
import "soundcloud.ts";
import "ipaddr.js";
// Dynamic imports should also be rewritten.
import("./a.js");
import("./a.mjs");
import("./a.cjs");
import("./react.js");
import("a-package/file.js");
// Bare dynamic import should not be rewritten.
import("soundcloud.ts");
// No-substitution template literal should also be rewritten.
import(`./a.js`);
// Non-string-literal dynamic import should not be rewritten.
import(dynamicPath);
Loading