use oxc::allocator::Allocator;
use oxc::ast::ast::SourceType;
use oxc::codegen::Codegen;
use oxc::codegen::CodegenOptions;
use oxc::parser::Parser;
use oxc::semantic::SemanticBuilder;
use oxc::transformer::JsxOptions;
use oxc::transformer::JsxRuntime;
use oxc::transformer::TransformOptions;
use oxc::transformer::Transformer;
use oxc::transformer::TypeScriptOptions;
use super::tsconfig::JsxMode;
use super::TransformerContext;
use super::TransformerResult;
#[derive(Debug, Clone, Default)]
struct JsxConfig {
runtime: Option<JsxRuntime>,
pragma: Option<String>,
pragma_frag: Option<String>,
import_source: Option<String>,
development: bool,
preserve: bool,
}
fn jsx_config_from_tsconfig(ctx: &TransformerContext) -> JsxConfig {
let tsconfig = &ctx.tsconfig;
let pragma = tsconfig
.jsx_factory
.clone()
.unwrap_or_else(|| "React.createElement".to_string());
let pragma_frag = tsconfig
.jsx_fragment_factory
.clone()
.unwrap_or_else(|| "React.Fragment".to_string());
let classic = || JsxConfig {
runtime: Some(JsxRuntime::Classic),
pragma: Some(pragma.clone()),
pragma_frag: Some(pragma_frag.clone()),
..JsxConfig::default()
};
let automatic = |development: bool| JsxConfig {
runtime: Some(JsxRuntime::Automatic),
import_source: Some(
tsconfig
.jsx_import_source
.clone()
.unwrap_or_else(|| "react".to_string()),
),
development,
..JsxConfig::default()
};
match tsconfig.jsx {
Some(JsxMode::React) => classic(),
Some(JsxMode::ReactJsx) => automatic(false),
Some(JsxMode::ReactJsxDev) => automatic(true),
Some(JsxMode::Preserve) | Some(JsxMode::ReactNative) => JsxConfig {
preserve: true,
..JsxConfig::default()
},
None => classic(),
}
}
pub fn transpile(ctx: TransformerContext) -> anyhow::Result<TransformerResult> {
let jsx_config = jsx_config_from_tsconfig(&ctx);
let source = String::from_utf8(ctx.content)?;
let allocator = Allocator::default();
let source_type = match ctx.kind.as_str() {
"ts" => SourceType::ts(),
"tsx" => SourceType::tsx(),
_ => {
anyhow::bail!("Got an invalid file type");
}
};
let parse_result = Parser::new(&allocator, &source, source_type).parse();
if !parse_result.diagnostics.is_empty() {
if ctx.kind == "ts" {
let allocator_tsx = Allocator::default();
let parse_as_tsx = Parser::new(&allocator_tsx, &source, SourceType::tsx()).parse();
let looks_like_jsx = source.contains("</") || source.contains("/>") || source.contains("<>");
if parse_as_tsx.diagnostics.is_empty() && looks_like_jsx {
anyhow::bail!(
"JSX syntax detected in a .ts file: {}. Rename the file to .tsx or remove JSX.",
ctx.path.display()
);
}
}
let errors: Vec<String> = parse_result
.diagnostics
.iter()
.map(|e| format!("{:?}", e))
.collect();
anyhow::bail!("Parse errors: {}", errors.join(", "));
}
let mut program = parse_result.program;
let scoping_result = SemanticBuilder::new().build(&program);
if !scoping_result.diagnostics.is_empty() {
let errors: Vec<String> = scoping_result
.diagnostics
.iter()
.map(|e| format!("{:?}", e))
.collect();
anyhow::bail!("Parse errors: {}", errors.join(", "));
}
let scoping = scoping_result.semantic.into_scoping();
let mut jsx = if source_type.is_jsx() && !jsx_config.preserve {
JsxOptions {
runtime: jsx_config.runtime.unwrap_or(JsxRuntime::Classic),
development: jsx_config.development,
import_source: jsx_config.import_source.clone(),
pragma: jsx_config.pragma.clone(),
pragma_frag: jsx_config.pragma_frag.clone(),
..JsxOptions::default()
}
} else {
JsxOptions::disable()
};
jsx.conform();
let typescript = TypeScriptOptions {
jsx_pragma: jsx_config
.pragma
.clone()
.unwrap_or_else(|| "React.createElement".to_string())
.into(),
jsx_pragma_frag: jsx_config
.pragma_frag
.clone()
.unwrap_or_else(|| "React.Fragment".to_string())
.into(),
..TypeScriptOptions::default()
};
let transform_options = TransformOptions {
typescript,
jsx,
..Default::default()
};
let result = Transformer::new(&allocator, &ctx.path, &transform_options);
let build_result = result.build_with_scoping(scoping, &mut program);
if !build_result.diagnostics.is_empty() {
let errors: Vec<String> = build_result
.diagnostics
.iter()
.map(|e| format!("{:?}", e))
.collect();
anyhow::bail!("Transform errors: {}", errors.join(", "));
}
let generated = Codegen::new()
.with_options(CodegenOptions {
minify: false,
..CodegenOptions::default()
})
.build(&program);
Ok(TransformerResult {
code: generated.code,
})
}