use std::borrow::Cow;
use std::sync::Arc;
use deno_media_type::MediaType;
use dprint_swc_ext::common::SourceRangedForSpanned;
use dprint_swc_ext::common::SourceTextInfo;
use swc_ecma_visit::fold_pass;
use thiserror::Error;
use crate::EmitError;
use crate::EmitOptions;
use crate::EmittedSourceText;
use crate::Globals;
use crate::Marks;
use crate::ModuleKind;
use crate::ModuleSpecifier;
use crate::ParseDiagnostic;
use crate::ParseDiagnosticInner;
use crate::ParseDiagnostics;
use crate::ParseDiagnosticsError;
use crate::ParsedSource;
use crate::ProgramRef;
use crate::SourceMap;
use crate::diagnostics::DiagnosticCollector;
use crate::diagnostics::SwcFoldDiagnosticsError;
use crate::diagnostics::ensure_no_fatal_swc_diagnostics;
use crate::emit;
use crate::swc::ast::Program;
use crate::swc::common::comments::SingleThreadedComments;
use crate::swc::ecma_visit::visit_mut_pass;
use crate::swc::parser::error::SyntaxError;
use crate::swc::transforms::fixer;
use crate::swc::transforms::helpers;
use crate::swc::transforms::hygiene;
use crate::swc::transforms::proposal;
use crate::swc::transforms::react;
use crate::swc::transforms::resolver;
use crate::swc::transforms::typescript;
use crate::swc::visit::Optional;
use deno_error::JsError;
mod jsx_precompile;
mod transforms;
#[derive(Debug, Clone)]
pub enum TranspileResult {
Cloned(EmittedSourceText),
Owned(EmittedSourceText),
}
impl TranspileResult {
pub fn into_source(self) -> EmittedSourceText {
match self {
TranspileResult::Owned(source) => source,
TranspileResult::Cloned(source) => source,
}
}
}
#[derive(Debug, Error, JsError)]
pub enum TranspileError {
#[class(inherit)]
#[error(transparent)]
ParseErrors(#[from] ParseDiagnosticsError),
#[class(inherit)]
#[error(transparent)]
FoldProgram(#[from] FoldProgramError),
#[class(type)]
#[error("{0}")]
EmitDiagnostic(String),
#[class(inherit)]
#[error(transparent)]
Emit(#[from] EmitError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ImportsNotUsedAsValues {
Remove,
Preserve,
Error,
}
#[derive(Debug, Clone, Hash)]
pub struct JsxClassicOptions {
pub factory: String,
pub fragment_factory: String,
}
impl Default for JsxClassicOptions {
fn default() -> Self {
Self {
factory: "React.createElement".into(),
fragment_factory: "React.Fragment".into(),
}
}
}
#[derive(Debug, Clone, Hash)]
pub struct JsxAutomaticOptions {
pub development: bool,
pub import_source: Option<String>,
}
#[derive(Debug, Clone, Hash)]
pub struct JsxPrecompileOptions {
pub automatic: JsxAutomaticOptions,
pub skip_elements: Option<Vec<String>>,
pub dynamic_props: Option<Vec<String>>,
}
#[derive(Debug, Clone, Hash)]
pub enum JsxRuntime {
Classic(JsxClassicOptions),
Automatic(JsxAutomaticOptions),
Precompile(JsxPrecompileOptions),
}
impl Default for JsxRuntime {
fn default() -> Self {
Self::Classic(Default::default())
}
}
impl JsxRuntime {
pub fn automatic(&self) -> Option<&JsxAutomaticOptions> {
match self {
JsxRuntime::Classic(_) => None,
JsxRuntime::Automatic(o) => Some(o),
JsxRuntime::Precompile(o) => Some(&o.automatic),
}
}
pub fn classic(&self) -> Option<&JsxClassicOptions> {
match self {
JsxRuntime::Classic(o) => Some(o),
JsxRuntime::Automatic(_) | JsxRuntime::Precompile(_) => None,
}
}
pub fn precompile(&self) -> Option<&JsxPrecompileOptions> {
match self {
JsxRuntime::Classic(_) | JsxRuntime::Automatic(_) => None,
JsxRuntime::Precompile(o) => Some(o),
}
}
}
#[derive(Debug, Default, Clone, Hash)]
pub enum DecoratorsTranspileOption {
#[default]
None,
Ecma,
LegacyTypeScript {
emit_metadata: bool,
},
}
#[derive(Debug, Clone, Hash)]
pub struct TranspileOptions {
pub decorators: DecoratorsTranspileOption,
pub verbatim_module_syntax: bool,
pub imports_not_used_as_values: ImportsNotUsedAsValues,
pub jsx: Option<JsxRuntime>,
pub var_decl_imports: bool,
}
impl Default for TranspileOptions {
fn default() -> Self {
TranspileOptions {
decorators: DecoratorsTranspileOption::default(),
verbatim_module_syntax: false,
imports_not_used_as_values: ImportsNotUsedAsValues::Remove,
jsx: Some(Default::default()),
var_decl_imports: false,
}
}
}
impl TranspileOptions {
fn as_tsx_config(&self) -> typescript::TsxConfig {
typescript::TsxConfig {
pragma: self
.jsx
.as_ref()
.and_then(|j| j.classic())
.map(|jsx| jsx.factory.clone().into()),
pragma_frag: self
.jsx
.as_ref()
.and_then(|j| j.classic())
.map(|jsx| jsx.fragment_factory.clone().into()),
}
}
fn as_typescript_config(&self) -> typescript::Config {
typescript::Config {
verbatim_module_syntax: self.verbatim_module_syntax,
import_not_used_as_values: match self.imports_not_used_as_values {
ImportsNotUsedAsValues::Remove => {
typescript::ImportsNotUsedAsValues::Remove
}
ImportsNotUsedAsValues::Preserve => {
typescript::ImportsNotUsedAsValues::Preserve
}
ImportsNotUsedAsValues::Error => {
typescript::ImportsNotUsedAsValues::Remove
}
},
no_empty_export: true,
import_export_assign_config:
typescript::TsImportExportAssignConfig::Preserve,
ts_enum_is_mutable: true,
native_class_properties: false,
}
}
}
#[derive(Debug, Default, Clone, Hash)]
pub struct TranspileModuleOptions {
pub module_kind: Option<ModuleKind>,
}
impl ParsedSource {
pub fn transpile(
self,
transpile_options: &TranspileOptions,
transpile_module_options: &TranspileModuleOptions,
emit_options: &EmitOptions,
) -> Result<TranspileResult, TranspileError> {
match self.transpile_owned(
transpile_options,
transpile_module_options,
emit_options,
) {
Ok(result) => Ok(TranspileResult::Owned(result?)),
Err(parsed_source) => {
parsed_source
.transpile_cloned(
transpile_options,
transpile_module_options,
emit_options,
)
.map(TranspileResult::Cloned)
}
}
}
fn transpile_cloned(
&self,
transpile_options: &TranspileOptions,
transpile_module_options: &TranspileModuleOptions,
emit_options: &EmitOptions,
) -> Result<EmittedSourceText, TranspileError> {
let program = (*self.program()).clone();
transpile(
self.specifier().clone(),
self.media_type(),
self.text().to_string(),
program,
self.comments().as_single_threaded(),
self.globals(),
&resolve_transpile_options(self.0.media_type, transpile_options),
transpile_module_options,
emit_options,
&self.0.diagnostics,
)
}
fn transpile_owned(
self,
transpile_options: &TranspileOptions,
transpile_module_options: &TranspileModuleOptions,
emit_options: &EmitOptions,
) -> Result<Result<EmittedSourceText, TranspileError>, ParsedSource> {
let inner = match Arc::try_unwrap(self.0) {
Ok(inner) => inner,
Err(inner) => return Err(ParsedSource(inner)),
};
let program = match Arc::try_unwrap(inner.program) {
Ok(program) => program,
Err(program) => {
return Err(ParsedSource(Arc::new(crate::ParsedSourceInner {
specifier: inner.specifier,
media_type: inner.media_type,
text: inner.text,
source_text_info: inner.source_text_info,
comments: inner.comments,
program,
tokens: inner.tokens,
syntax_contexts: inner.syntax_contexts,
diagnostics: inner.diagnostics,
globals: inner.globals,
})));
}
};
Ok(transpile(
inner.specifier,
inner.media_type,
inner.text.to_string(),
program,
inner.comments.into_single_threaded(),
&inner.globals,
&resolve_transpile_options(inner.media_type, transpile_options),
transpile_module_options,
emit_options,
&inner.diagnostics,
))
}
}
fn resolve_transpile_options(
media_type: MediaType,
options: &TranspileOptions,
) -> Cow<'_, TranspileOptions> {
if options.jsx.is_some() {
let allows_jsx = matches!(media_type, MediaType::Jsx | MediaType::Tsx);
if !allows_jsx {
return Cow::Owned(TranspileOptions {
jsx: None,
..(options.clone())
});
}
}
Cow::Borrowed(options)
}
#[allow(clippy::too_many_arguments)]
fn transpile(
specifier: ModuleSpecifier,
media_type: MediaType,
source: String,
program: Program,
comments: SingleThreadedComments,
globals: &Globals,
transpile_options: &TranspileOptions,
transpile_module_options: &TranspileModuleOptions,
emit_options: &EmitOptions,
diagnostics: &ParseDiagnostics,
) -> Result<EmittedSourceText, TranspileError> {
let module_kind = transpile_module_options.module_kind.unwrap_or({
if matches!(media_type, MediaType::Cjs | MediaType::Cts) {
ModuleKind::Cjs
} else {
ModuleKind::Esm
}
});
let program = match program {
Program::Module(module) => match module_kind {
ModuleKind::Cjs if ProgramRef::Module(&module).compute_is_script() => {
Program::Script(convert_script_module_to_swc_script(
&specifier, &source, module,
)?)
}
_ => Program::Module(module),
},
Program::Script(script) => {
match module_kind {
ModuleKind::Esm => {
Program::Module(crate::swc::ast::Module {
span: script.span,
body: script
.body
.into_iter()
.map(crate::swc::ast::ModuleItem::Stmt)
.collect(),
shebang: script.shebang,
})
}
ModuleKind::Cjs => Program::Script(script),
}
}
};
let source_map = SourceMap::single(specifier, source);
let diagnostics = diagnostics.for_module_kind(module_kind);
let program = globals.with(|marks| {
fold_program(
program,
transpile_options,
&source_map,
&comments,
marks,
diagnostics,
)
})?;
Ok(emit(
(&program).into(),
&comments,
&source_map,
emit_options,
)?)
}
fn convert_script_module_to_swc_script(
specifier: &ModuleSpecifier,
source: &str,
module: crate::swc::ast::Module,
) -> Result<crate::swc::ast::Script, TranspileError> {
use crate::swc::ast::*;
use crate::swc::common::DUMMY_SP;
use crate::swc::common::SyntaxContext;
fn ts_entity_name_to_expr(ts_entity_name: TsEntityName) -> Expr {
match ts_entity_name {
TsEntityName::Ident(ident) => Expr::Ident(ident),
TsEntityName::TsQualifiedName(ts_qualified_name) => {
ts_qualified_name_to_expr(*ts_qualified_name)
}
}
}
fn ts_qualified_name_to_expr(ts_qualified_name: TsQualifiedName) -> Expr {
match ts_qualified_name.left {
TsEntityName::Ident(ident) => Expr::Member(MemberExpr {
span: DUMMY_SP,
obj: Box::new(Expr::Ident(ident)),
prop: MemberProp::Ident(IdentName::new(
ts_qualified_name.right.sym,
ts_qualified_name.right.span,
)),
}),
TsEntityName::TsQualifiedName(ts_qualified_name) => {
Expr::Member(MemberExpr {
span: DUMMY_SP,
obj: Box::new(ts_entity_name_to_expr(ts_qualified_name.left)),
prop: MemberProp::Ident(IdentName::new(
ts_qualified_name.right.sym,
ts_qualified_name.right.span,
)),
})
}
}
}
fn module_decl_to_stmt(
specifier: &ModuleSpecifier,
source: &str,
decl: ModuleDecl,
stmts: &mut Vec<Stmt>,
) -> Result<(), TranspileError> {
match decl {
ModuleDecl::Import(_)
| ModuleDecl::ExportDecl(_)
| ModuleDecl::ExportNamed(_)
| ModuleDecl::ExportDefaultDecl(_)
| ModuleDecl::ExportDefaultExpr(_)
| ModuleDecl::ExportAll(_) => {
return Err(TranspileError::ParseErrors(ParseDiagnosticsError(vec![
ParseDiagnostic(Box::new(ParseDiagnosticInner {
specifier: specifier.clone(),
range: decl.range(),
kind: SyntaxError::ImportExportInScript,
source: SourceTextInfo::new(source.into()),
})),
])));
}
ModuleDecl::TsImportEquals(ts_import_equals_decl) => {
if ts_import_equals_decl.is_type_only {
return Ok(());
}
let export_ident = if ts_import_equals_decl.is_export {
Some(ts_import_equals_decl.id.clone())
} else {
None
};
let var_decl = VarDecl {
span: ts_import_equals_decl.span,
ctxt: ts_import_equals_decl.id.ctxt,
kind: VarDeclKind::Const,
declare: false,
decls: Vec::from([VarDeclarator {
span: ts_import_equals_decl.span,
name: ts_import_equals_decl.id.into(),
init: Some(match ts_import_equals_decl.module_ref {
TsModuleRef::TsEntityName(ts_entity_name) => {
Box::new(ts_entity_name_to_expr(ts_entity_name))
}
TsModuleRef::TsExternalModuleRef(ts_external_module_ref) => {
Box::new(Expr::Call(CallExpr {
span: DUMMY_SP,
callee: Callee::Expr(Box::new(Expr::Ident(Ident::new(
"require".into(),
DUMMY_SP,
SyntaxContext::empty(),
)))),
ctxt: SyntaxContext::empty(),
args: vec![ExprOrSpread {
expr: Box::new(Expr::Lit(Lit::Str(Str {
span: ts_external_module_ref.span,
value: ts_external_module_ref.expr.value.clone(),
raw: None,
}))),
spread: None,
}],
type_args: None,
}))
}
}),
definite: false,
}]),
};
stmts.push(Stmt::Decl(Decl::Var(Box::new(var_decl))));
if let Some(export_ident) = export_ident {
stmts.push(Stmt::Expr(ExprStmt {
span: DUMMY_SP,
expr: Box::new(Expr::Assign(AssignExpr {
span: DUMMY_SP,
op: AssignOp::Assign,
left: AssignTarget::Simple(SimpleAssignTarget::Member(
MemberExpr {
span: DUMMY_SP,
obj: Box::new(Expr::Ident(Ident {
span: DUMMY_SP,
ctxt: export_ident.ctxt,
sym: "exports".into(),
optional: false,
})),
prop: MemberProp::Ident(IdentName::new(
export_ident.sym.clone(),
export_ident.span,
)),
},
)),
right: Box::new(Expr::Ident(Ident::new(
export_ident.sym,
export_ident.span,
export_ident.ctxt,
))),
})),
}));
}
}
ModuleDecl::TsExportAssignment(ts_export_assignment) => {
stmts.push(Stmt::Expr(ExprStmt {
span: DUMMY_SP,
expr: Box::new(Expr::Assign(AssignExpr {
span: DUMMY_SP,
op: AssignOp::Assign,
left: AssignTarget::Simple(SimpleAssignTarget::Member(
MemberExpr {
span: DUMMY_SP,
obj: Box::new(Expr::Ident(Ident::new(
"module".into(),
DUMMY_SP,
SyntaxContext::empty(),
))),
prop: MemberProp::Ident(IdentName::new(
"exports".into(),
DUMMY_SP,
)),
},
)),
right: ts_export_assignment.expr,
})),
}))
}
ModuleDecl::TsNamespaceExport(_) => {
}
}
Ok(())
}
let module_decl_count = module
.body
.iter()
.filter(|m| matches!(m, ModuleItem::ModuleDecl(_)))
.count();
let mut stmts = Vec::with_capacity(module.body.len() + module_decl_count);
for module_item in module.body {
match module_item {
ModuleItem::Stmt(stmt) => {
stmts.push(stmt);
}
ModuleItem::ModuleDecl(decl) => {
module_decl_to_stmt(specifier, source, decl, &mut stmts)?;
}
}
}
Ok(Script {
span: module.span,
body: stmts,
shebang: module.shebang,
})
}
#[derive(Debug, Error, JsError)]
pub enum FoldProgramError {
#[class(inherit)]
#[error(transparent)]
ParseDiagnostics(#[from] ParseDiagnosticsError),
#[class(inherit)]
#[error(transparent)]
Swc(#[from] SwcFoldDiagnosticsError),
}
pub fn fold_program<'a>(
program: Program,
options: &TranspileOptions,
source_map: &SourceMap,
comments: &SingleThreadedComments,
marks: &Marks,
diagnostics: Box<dyn Iterator<Item = &'a ParseDiagnostic> + 'a>,
) -> Result<Program, FoldProgramError> {
ensure_no_fatal_diagnostics(diagnostics)?;
let passes = (
Optional::new(
fold_pass(transforms::StripExportsFolder),
options.var_decl_imports,
),
resolver(marks.unresolved, marks.top_level, true),
Optional::new(
proposal::decorators::decorators(proposal::decorators::Config {
legacy: true,
emit_metadata: matches!(
options.decorators,
DecoratorsTranspileOption::LegacyTypeScript {
emit_metadata: true
}
),
use_define_for_class_fields: true,
}),
matches!(
options.decorators,
DecoratorsTranspileOption::LegacyTypeScript { .. }
),
),
Optional::new(
proposal::decorator_2022_03::decorator_2022_03(),
matches!(options.decorators, DecoratorsTranspileOption::Ecma),
),
helpers::inject_helpers(marks.top_level),
Optional::new(
fold_pass(transforms::ImportDeclsToVarDeclsFolder),
options.var_decl_imports,
),
Optional::new(
typescript::typescript(
options.as_typescript_config(),
marks.unresolved,
marks.top_level,
),
options.jsx.is_none(),
),
Optional::new(
typescript::tsx(
source_map.inner().clone(),
options.as_typescript_config(),
options.as_tsx_config(),
comments,
marks.unresolved,
marks.top_level,
),
options.jsx.is_some(),
),
Optional::new(
visit_mut_pass(jsx_precompile::JsxPrecompile::new(
options
.jsx
.as_ref()
.and_then(|jsx| jsx.automatic())
.and_then(|a| a.import_source.clone()),
options
.jsx
.as_ref()
.and_then(|jsx| jsx.precompile())
.and_then(|p| p.skip_elements.clone()),
options
.jsx
.as_ref()
.and_then(|jsx| jsx.precompile())
.and_then(|p| p.dynamic_props.clone()),
)),
options
.jsx
.as_ref()
.map(|jsx| jsx.precompile().is_some())
.unwrap_or(false),
),
Optional::new(
react::react(
source_map.inner().clone(),
Some(comments),
#[allow(deprecated)]
react::Options {
pragma: options
.jsx
.as_ref()
.and_then(|jsx| jsx.classic())
.map(|jsx| jsx.factory.clone().into()),
pragma_frag: options
.jsx
.as_ref()
.and_then(|jsx| jsx.classic())
.map(|jsx| jsx.fragment_factory.clone().into()),
use_builtins: Some(true),
runtime: if options
.jsx
.as_ref()
.map(|jsx| matches!(jsx, JsxRuntime::Automatic(..)))
.unwrap_or(false)
{
Some(react::Runtime::Automatic)
} else {
None
},
development: options
.jsx
.as_ref()
.and_then(|o| o.automatic())
.map(|o| o.development),
import_source: options
.jsx
.as_ref()
.and_then(|o| o.automatic())
.and_then(|o| o.import_source.as_deref())
.map(From::from),
next: None,
refresh: None,
throw_if_namespace: Some(false),
use_spread: None,
},
marks.top_level,
marks.unresolved,
),
options.jsx.is_some(),
),
Optional::new(
fold_pass(transforms::ImportDeclsToVarDeclsFolder),
options.var_decl_imports && options.jsx.is_some(),
),
(fixer(Some(comments)), hygiene()),
);
let emitter = DiagnosticCollector::default();
let (handler, diagnostics_cell) = emitter.into_handler_and_cell();
let result = crate::swc::common::errors::HANDLER.set(&handler, || {
helpers::HELPERS
.set(&helpers::Helpers::new(false), || program.apply(passes))
});
let mut diagnostics = diagnostics_cell.borrow_mut();
let diagnostics = std::mem::take(&mut *diagnostics);
ensure_no_fatal_swc_diagnostics(source_map.inner(), diagnostics.into_iter())?;
Ok(result)
}
fn ensure_no_fatal_diagnostics<'a>(
diagnostics: Box<dyn Iterator<Item = &'a ParseDiagnostic> + 'a>,
) -> Result<(), ParseDiagnosticsError> {
let fatal_diagnostics = diagnostics
.filter(|d| is_fatal_syntax_error(d.kind()))
.map(ToOwned::to_owned)
.collect::<Vec<_>>();
if !fatal_diagnostics.is_empty() {
Err(ParseDiagnosticsError(fatal_diagnostics))
} else {
Ok(())
}
}
fn is_fatal_syntax_error(error_kind: &SyntaxError) -> bool {
matches!(
error_kind,
SyntaxError::TS1003 |
SyntaxError::TS1005 |
SyntaxError::TS1085 |
SyntaxError::LegacyOctal |
SyntaxError::LegacyDecimal |
SyntaxError::TS1109 |
SyntaxError::TS2406 |
SyntaxError::UnterminatedStrLit |
SyntaxError::NullishCoalescingWithLogicalOp |
SyntaxError::InitRequiredForUsingDecl |
SyntaxError::Expected(_, _)
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MediaType;
use crate::ModuleSpecifier;
use crate::ParseParams;
use crate::SourceMapOption;
use crate::parse_program;
use base64::Engine;
use pretty_assertions::assert_eq;
#[test]
fn test_transpile() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"
enum D {
A,
B,
}
const enum E {
A,
B,
}
console.log(E.A);
namespace N {
export enum D {
A = "value"
}
export const Value = 5;
}
export class A {
private b: string;
protected c: number = 1;
e: "foo";
constructor (public d = D.A) {
const e = "foo" as const;
this.e = e;
console.log(N.Value);
}
}
"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let transpiled_source = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source();
let expected_text = r#"var D = /*#__PURE__*/ function(D) {
D[D["A"] = 0] = "A";
D[D["B"] = 1] = "B";
return D;
}(D || {});
console.log(0);
(function(N) {
(function(D) {
D["A"] = "value";
})(N.D || (N.D = {}));
N.Value = 5;
})(N || (N = {}));
export class A {
d;
b;
c;
e;
constructor(d = D.A){
this.d = d;
this.c = 1;
const e = "foo";
this.e = e;
console.log(N.Value);
}
}
var N;
"#;
assert_eq!(
&transpiled_source.text[..expected_text.len()],
expected_text
);
assert!(
transpiled_source
.text
.contains("\n//# sourceMappingURL=data:application/json;base64,")
);
assert!(transpiled_source.source_map.is_none());
}
#[test]
fn test_explicit_resource_management() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"Deno.test({
permissions: {
net: true
}
}, async function listenerExplicitResourceManagement() {
let done;
{
using listener = Deno.listen({
port: listenPort
});
done = assertRejects(()=>listener.accept(), Deno.errors.BadResource);
}
await done;
});
"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let transpiled_source = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source();
let expected_text = source; assert_eq!(
&transpiled_source.text[..expected_text.len()],
expected_text
);
}
#[test]
fn test_transpile_tsx() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"
export class A {
render() {
return <div><span></span></div>
}
}
"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Tsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true, })
.unwrap();
let transpiled_source = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source();
assert!(
transpiled_source
.text
.contains("React.createElement(\"div\", null")
);
}
#[test]
fn test_transpile_tsx_with_namespace() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"
export class A {
render() {
return <my:tag><span my:attr="this"></span></my:tag>
}
}
"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Tsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true, })
.unwrap();
let transpiled_source = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source();
assert!(
transpiled_source
.text
.contains("React.createElement(\"my:tag\", null")
);
assert!(transpiled_source.text.contains("\"my:attr\": \"this\""));
}
#[test]
fn test_transpile_jsx_pragma() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"
/** @jsx h */
/** @jsxFrag Fragment */
import { h, Fragment } from "https://deno.land/x/mod.ts";
function App() {
return (
<div><></></div>
);
}"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Jsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true,
})
.unwrap();
let code = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions {
remove_comments: true,
..Default::default()
},
)
.unwrap()
.into_source()
.text;
let expected = r#"import { h, Fragment } from "https://deno.land/x/mod.ts";
function App() {
return h("div", null, h(Fragment, null));
}"#;
assert_eq!(&code[..expected.len()], expected);
}
#[test]
fn test_transpile_jsx_import_source_pragma() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.tsx").unwrap();
let source = r#"
/** @jsxImportSource jsx_lib */
function App() {
return (
<div><></></div>
);
}"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Jsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true,
})
.unwrap();
let code = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions {
remove_comments: false,
..Default::default()
},
)
.unwrap()
.into_source()
.text;
let expected = r#"import { jsx as _jsx, Fragment as _Fragment } from "jsx_lib/jsx-runtime";
/** @jsxImportSource jsx_lib */ function App() {
return /*#__PURE__*/ _jsx("div", {
children: /*#__PURE__*/ _jsx(_Fragment, {})
});
"#;
assert_eq!(&code[..expected.len()], expected);
}
#[test]
fn test_transpile_jsx_import_source_no_pragma() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.tsx").unwrap();
let source = r#"
function App() {
return (
<div><></></div>
);
}"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Jsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true,
})
.unwrap();
let transpile_options = TranspileOptions {
jsx: Some(JsxRuntime::Automatic(JsxAutomaticOptions {
import_source: Some("jsx_lib".to_string()),
development: false,
})),
..Default::default()
};
let code = program
.transpile(
&transpile_options,
&TranspileModuleOptions::default(),
&EmitOptions {
remove_comments: true,
..Default::default()
},
)
.unwrap()
.into_source()
.text;
let expected = r#"import { jsx as _jsx, Fragment as _Fragment } from "jsx_lib/jsx-runtime";
function App() {
return _jsx("div", {
children: _jsx(_Fragment, {})
});
}
"#;
assert_eq!(&code[..expected.len()], expected);
}
#[test]
fn test_transpile_jsx_import_source_no_pragma_dev() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.tsx").unwrap();
let source = r#"function App() {
return (
<div><></></div>
);
}"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Jsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true,
})
.unwrap();
let transpile_options = TranspileOptions {
jsx: Some(JsxRuntime::Automatic(JsxAutomaticOptions {
import_source: Some("jsx_lib".to_string()),
development: true,
})),
..Default::default()
};
let code = program
.transpile(
&transpile_options,
&TranspileModuleOptions::default(),
&EmitOptions {
remove_comments: true,
..Default::default()
},
)
.unwrap()
.into_source()
.text;
let expected = r#"import { jsxDEV as _jsxDEV, Fragment as _Fragment } from "jsx_lib/jsx-dev-runtime";
function App() {
return _jsxDEV("div", {
children: _jsxDEV(_Fragment, {}, void 0, false)
}, void 0, false, {
fileName: "https://deno.land/x/mod.tsx",
lineNumber: 3,
columnNumber: 5
}, this);
}
"#;
assert_eq!(&code[..expected.len()], expected);
}
#[test]
fn test_transpile_jsx_import_source_pragma_var_decl_imports() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.tsx").unwrap();
let source = r#"
/** @jsxImportSource jsx_lib */
import * as example from "example";
function App() {
return (
<div><></></div>
);
}"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Jsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true,
})
.unwrap();
let transpile_options = TranspileOptions {
var_decl_imports: true,
..Default::default()
};
let code = program
.transpile(
&transpile_options,
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source()
.text;
let expected = r#"/** @jsxImportSource jsx_lib */ const { "jsx": _jsx, "Fragment": _Fragment } = await import("jsx_lib/jsx-runtime");
const example = await import("example");
function App() {
return /*#__PURE__*/ _jsx("div", {
children: /*#__PURE__*/ _jsx(_Fragment, {})
});
"#;
assert_eq!(&code[..expected.len()], expected);
}
#[test]
fn test_transpile_jsx_import_source_cjs() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.tsx").unwrap();
let source = r#"
function App() {
return (
<div><></></div>
);
}"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Jsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true,
})
.unwrap();
let transpile_options = TranspileOptions {
jsx: Some(JsxRuntime::Automatic(JsxAutomaticOptions {
import_source: Some("jsx_lib".to_string()),
development: false,
})),
..Default::default()
};
let transpile_module_options = TranspileModuleOptions {
module_kind: Some(ModuleKind::Cjs), };
let code = program
.transpile(
&transpile_options,
&transpile_module_options,
&EmitOptions {
remove_comments: true,
..Default::default()
},
)
.unwrap()
.into_source()
.text;
let expected = r#"const { jsx: _jsx, Fragment: _Fragment } = require("jsx_lib/jsx-runtime");
function App() {
return _jsx("div", {
children: _jsx(_Fragment, {})
});
}
"#;
assert_eq!(&code[..expected.len()], expected);
}
#[test]
fn test_transpile_jsx_import_source_cjs_with_import_export() {
let specifier = ModuleSpecifier::parse("file:///mod.tsx").unwrap();
let source = r#"
import test = require('./test');
export import other = require('./test');
console.log(test);
console.log(other);
export import asdf = other.add;
function App() {
return (
<div><></></div>
);
}"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Tsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true,
})
.unwrap();
let transpile_options = TranspileOptions {
jsx: Some(JsxRuntime::Automatic(JsxAutomaticOptions {
import_source: Some("jsx_lib".to_string()),
development: false,
})),
..Default::default()
};
let transpile_module_options = TranspileModuleOptions {
module_kind: Some(ModuleKind::Cjs), };
let code = program
.transpile(
&transpile_options,
&transpile_module_options,
&EmitOptions {
remove_comments: true,
..Default::default()
},
)
.unwrap()
.into_source()
.text;
let expected = r#"const { jsx: _jsx, Fragment: _Fragment } = require("jsx_lib/jsx-runtime");
const test = require("./test");
const other = require("./test");
exports.other = other;
console.log(test);
console.log(other);
const asdf = other.add;
exports.asdf = asdf;
function App() {
return _jsx("div", {
children: _jsx(_Fragment, {})
});
}
"#;
assert_eq!(&code[..expected.len()], expected);
}
#[test]
fn test_transpile_jsx_import_source_cjs_with_ts_export_equals() {
let specifier = ModuleSpecifier::parse("file:///mod.tsx").unwrap();
let source = r#"
export = function App() {
return (
<div><></></div>
);
}"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Tsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true,
})
.unwrap();
let transpile_options = TranspileOptions {
jsx: Some(JsxRuntime::Automatic(JsxAutomaticOptions {
import_source: Some("jsx_lib".to_string()),
development: false,
})),
..Default::default()
};
let transpile_module_options = TranspileModuleOptions {
module_kind: Some(ModuleKind::Cjs), };
let code = program
.transpile(
&transpile_options,
&transpile_module_options,
&EmitOptions {
remove_comments: true,
..Default::default()
},
)
.unwrap()
.into_source()
.text;
let expected = r#"const { jsx: _jsx, Fragment: _Fragment } = require("jsx_lib/jsx-runtime");
module.exports = function App() {
return _jsx("div", {
children: _jsx(_Fragment, {})
});
};
"#;
assert_eq!(&code[..expected.len()], expected);
}
#[test]
fn test_transpile_decorators() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"
function enumerable(value: boolean) {
return function (
_target: any,
_propertyKey: string,
descriptor: PropertyDescriptor,
) {
descriptor.enumerable = value;
};
}
export class A {
@enumerable(false)
a() {
Test.value;
}
}
"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let code = program
.transpile(
&TranspileOptions {
decorators: DecoratorsTranspileOption::LegacyTypeScript {
emit_metadata: false,
},
..Default::default()
},
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source()
.text;
let expected = r#"function _ts_decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function enumerable(value) {
return function(_target, _propertyKey, descriptor) {
descriptor.enumerable = value;
};
}
export class A {
a() {
Test.value;
}
}
_ts_decorate([
enumerable(false)
], A.prototype, "a", null);"#;
assert_eq!(&code[0..expected.len()], expected);
}
#[test]
fn test_transpile_decorators_proposal() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"
function enumerable(value: boolean) {
return function (
_target: any,
_propertyKey: string,
descriptor: PropertyDescriptor,
) {
descriptor.enumerable = value;
return descriptor;
};
}
export class A {
@enumerable(false)
a() {
Test.value;
}
}
"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let code = program
.transpile(
&TranspileOptions {
decorators: DecoratorsTranspileOption::Ecma,
..Default::default()
},
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source()
.text;
let expected =
include_str!("./testdata/tc39_decorator_proposal_output.txt");
assert_eq!(
&code[0..std::cmp::min(expected.len(), code.len())],
expected
);
}
#[test]
fn test_transpile_no_decorators() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"
function enumerable(value: boolean) {
return function (
_target: any,
_propertyKey: string,
descriptor: PropertyDescriptor,
) {
descriptor.enumerable = value;
return descriptor;
};
}
export class A {
@enumerable(false)
a() {
Test.value;
}
}
"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let code = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source()
.text;
let expected = r#"function enumerable(value) {
return function(_target, _propertyKey, descriptor) {
descriptor.enumerable = value;
return descriptor;
};
}
export class A {
@enumerable(false)
a() {
Test.value;
}
}
"#;
assert_eq!(&code[0..expected.len()], expected);
}
#[test]
fn transpile_handle_code_nested_in_ts_nodes_with_jsx_pass() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"
export function g() {
let algorithm: any
algorithm = {}
return <Promise>(
test(algorithm, false, keyUsages)
)
}
"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let transpile_options = TranspileOptions {
jsx: Some(Default::default()),
..Default::default()
};
let code = program
.transpile(
&transpile_options,
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source()
.text;
let expected = r#"export function g() {
let algorithm;
algorithm = {};
return test(algorithm, false, keyUsages);
}"#;
assert_eq!(&code[..expected.len()], expected);
}
#[test]
fn transpile_bitshift_typescript() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"
for (let i = 0; i < testVariable >> 1; i++) callCount++;
"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let code = program
.transpile(
&Default::default(),
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source()
.text;
let expected = r#"for(let i = 0; i < testVariable >> 1; i++)callCount++;"#;
assert_eq!(&code[..expected.len()], expected);
}
#[test]
fn jsx_spread_works() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"const A = () => {
return <div>{...[]}</div>;
};"#;
let parsed_source = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Tsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
assert!(
parsed_source
.transpile(
&Default::default(),
&TranspileModuleOptions::default(),
&EmitOptions::default()
)
.is_ok()
);
}
#[test]
fn diagnostic_octal_and_leading_zero_num_literals() {
assert_eq!(
get_diagnostic("077"),
[
"SyntaxError: Legacy octal literals are not available when targeting ECMAScript 5 and higher",
" |",
"1 | 077",
" | ~~~",
" at https://deno.land/x/mod.ts:1:1",
"",
"SyntaxError: Legacy octal escape is not permitted in strict mode",
" |",
"1 | 077",
" | ~~~",
" at https://deno.land/x/mod.ts:1:1",
]
.join("\n")
);
assert_eq!(
get_diagnostic("099"),
[
"SyntaxError: Legacy decimal escape is not permitted in strict mode",
" |",
"1 | 099",
" | ~~~",
" at https://deno.land/x/mod.ts:1:1",
]
.join("\n")
);
}
#[test]
fn diagnostic_missing_brace() {
assert_eq!(
get_diagnostic("function test() {"),
[
"SyntaxError: Expected '}', got '<eof>'",
" |",
"1 | function test() {",
" | ~",
" at https://deno.land/x/mod.ts:1:18",
]
.join("\n"),
);
}
#[test]
fn diagnostic_nullish_coalescing_with_logical_op() {
assert_eq!(
get_diagnostic("null || undefined ?? 'foo';"),
[
"SyntaxError: Nullish coalescing operator(??) requires parens when mixing with logical operators",
" |",
"1 | null || undefined ?? 'foo';",
" | ~~~~~~~~~~~~~~~~~",
" at https://deno.land/x/mod.ts:1:1",
]
.join("\n")
);
assert_eq!(
get_diagnostic("null && undefined ?? 'foo';"),
[
"SyntaxError: Nullish coalescing operator(??) requires parens when mixing with logical operators",
" |",
"1 | null && undefined ?? 'foo';",
" | ~~~~~~~~~~~~~~~~~",
" at https://deno.land/x/mod.ts:1:1",
]
.join("\n"),
);
}
#[test]
fn diagnostic_missing_init_in_using() {
assert_eq!(
get_diagnostic("using test"),
[
"SyntaxError: Using declaration requires initializer",
" |",
"1 | using test",
" | ~~~~~~~~~~",
" at https://deno.land/x/mod.ts:1:1",
]
.join("\n")
);
}
#[test]
fn diagnostic_invalid_left_hand_side_of_assignment() {
assert_eq!(
get_diagnostic("(true ? a : b) = 1;"),
[
"SyntaxError: The left-hand side of an assignment expression must be a variable or a property access.",
" |",
"1 | (true ? a : b) = 1;",
" | ~~~~~~~~~~~~~~",
" at https://deno.land/x/mod.ts:1:1",
"",
"SyntaxError: The left-hand side of an assignment expression must be a variable or a property access.",
" |",
"1 | (true ? a : b) = 1;",
" | ~~~~~~~~~~~~~~",
" at https://deno.land/x/mod.ts:1:1",
]
.join("\n")
);
}
fn get_diagnostic(source: &str) -> String {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let parsed_source = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
parsed_source
.transpile(
&Default::default(),
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.err()
.unwrap()
.to_string()
}
#[test]
fn source_map_properly_encoded() {
let p = parse_program(ParseParams {
specifier: ModuleSpecifier::parse("file:///Users/ib/dev/deno/foo.ts")
.unwrap(),
text: r#"export default function () {
return "📣❓";
}"#
.into(),
media_type: MediaType::TypeScript,
capture_tokens: true,
scope_analysis: false,
maybe_syntax: None,
})
.unwrap();
let transpiled = p
.transpile(
&Default::default(),
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source();
let lines: Vec<&str> = transpiled.text.split('\n').collect();
let last_line = lines.last().unwrap();
let input = last_line
.trim_start_matches("//# sourceMappingURL=data:application/json;base64,");
base64::prelude::BASE64_STANDARD.decode(input).unwrap();
}
#[test]
fn test_precompile_jsx() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.tsx").unwrap();
let source = r#"const a = <Foo><span>hello</span>foo<Bar><p><span class="bar">asdf</span></p></Bar></Foo>;"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Tsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let transpile_options = TranspileOptions {
jsx: Some(JsxRuntime::Precompile(JsxPrecompileOptions {
automatic: JsxAutomaticOptions {
development: false,
import_source: None, },
skip_elements: Some(vec!["p".to_string()]),
dynamic_props: Some(vec!["class".to_string()]),
})),
..Default::default()
};
let code = program
.transpile(
&transpile_options,
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source()
.text;
let expected1 = r#"import { jsx as _jsx, jsxTemplate as _jsxTemplate, jsxAttr as _jsxAttr } from "react/jsx-runtime";
const $$_tpl_2 = [
"<span ",
">asdf</span>"
];
const $$_tpl_1 = [
"<span>hello</span>foo",
""
];
const a = _jsx(Foo, {
children: _jsxTemplate($$_tpl_1, _jsx(Bar, {
children: _jsx("p", {
children: _jsxTemplate($$_tpl_2, _jsxAttr("class", "bar"))
})
}))
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2Vz"#;
assert_eq!(&code[0..expected1.len()], expected1);
}
#[test]
fn test_verbatim_module_syntax() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"import type foo from "./foo.ts"; import bar from "./bar.ts"; import baz from "./baz.ts"; const b: baz = 1;"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let transpile_options = TranspileOptions {
verbatim_module_syntax: true,
..Default::default()
};
let code = program
.transpile(
&transpile_options,
&TranspileModuleOptions::default(),
&EmitOptions::default(),
)
.unwrap()
.into_source()
.text;
let expected1 = r#"import bar from "./bar.ts";
import baz from "./baz.ts";
const b = 1;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImh0dHBzOi8vZGVuby5sYW5kL3gvbW9kLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIGZvbyBmcm9tIFwiLi9mb28udHNcIjsgaW1wb3J0IGJhciBmcm9tIFwiLi9iYXIudHNcIjsgaW1wb3J0IGJheiBmcm9tIFwiLi9iYXoudHNcIjsgY29uc3QgYjogYmF6ID0gMTsiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQWlDLE9BQU8sU0FBUyxXQUFXO0FBQUMsT0FBTyxTQUFTLFdBQVc7QUFB"#;
assert_eq!(&code[0..expected1.len()], expected1);
}
#[test]
fn test_inline_source_map_newline() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.tsx").unwrap();
let source = r#"{ const foo = "bar"; };"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Tsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let emit_options = EmitOptions {
source_map: SourceMapOption::Inline,
..Default::default()
};
let emit_result = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&emit_options,
)
.unwrap()
.into_source();
let expected1 = r#"{
const foo = "bar";
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJza"#;
assert_eq!(&emit_result.text[0..expected1.len()], expected1);
assert_eq!(emit_result.source_map, None);
}
#[test]
fn test_source_map() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.tsx").unwrap();
let source = r#"{ const foo = "bar"; };"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Tsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let emit_options = EmitOptions {
source_map: SourceMapOption::Separate,
..Default::default()
};
let emit_result = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&emit_options,
)
.unwrap()
.into_source();
assert_eq!(
&emit_result.text,
r#"{
const foo = "bar";
}"#
);
assert_eq!(
emit_result.source_map.as_deref(),
Some(
r#"{"version":3,"sources":["https://deno.land/x/mod.tsx"],"sourcesContent":["{ const foo = \"bar\"; };"],"names":[],"mappings":"AAAA;EAAE,MAAM,MAAM;AAAO"}"#
)
);
}
#[test]
fn test_source_map_base() {
#[track_caller]
fn run_test(file_name: &str, base: &str, expected: &str) {
let specifier = ModuleSpecifier::parse(file_name).unwrap();
let source = r#"{ const foo = "bar"; };"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Tsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let emit_options = EmitOptions {
source_map: SourceMapOption::Separate,
source_map_base: Some(ModuleSpecifier::parse(base).unwrap()),
..Default::default()
};
let emit_result = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&emit_options,
)
.unwrap()
.into_source();
assert_eq!(
&emit_result.text,
r#"{
const foo = "bar";
}"#
);
let value: serde_json::Value =
serde_json::from_str(&emit_result.source_map.unwrap()).unwrap();
assert_eq!(
value,
serde_json::json!({
"version":3,
"sources":[expected],
"sourcesContent":["{ const foo = \"bar\"; };"],
"names":[],
"mappings":"AAAA;EAAE,MAAM,MAAM;AAAO"
})
);
}
run_test(
"https://deno.land/x/mod.tsx",
"https://deno.land/x/",
"mod.tsx",
);
run_test(
"https://deno.land/x/mod.tsx",
"https://deno.land/",
"x/mod.tsx",
);
run_test(
"https://deno.land/x/mod.tsx",
"file:///home/user/",
"https://deno.land/x/mod.tsx",
);
run_test(
"https://deno.land/x/mod.tsx",
"https://example.com/",
"https://deno.land/x/mod.tsx",
);
run_test(
"https://example.com/base/",
"https://example.com/base/",
"https://example.com/base/",
);
run_test("file:///example.ts", "file:///", "example.ts");
run_test(
"file:///sub_dir/example.ts",
"file:///",
"sub_dir/example.ts",
);
run_test(
"file:///sub_dir/example.ts",
"file:///sub_dir/",
"example.ts",
);
}
#[test]
fn test_source_map_with_file() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.tsx").unwrap();
let source = r#"{ const foo = "bar"; };"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Tsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let emit_options = EmitOptions {
source_map: SourceMapOption::Separate,
source_map_file: Some("mod.tsx".to_owned()),
..Default::default()
};
let emit_result = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&emit_options,
)
.unwrap()
.into_source();
assert_eq!(
&emit_result.text,
r#"{
const foo = "bar";
}"#
);
assert_eq!(
emit_result.source_map.as_deref(),
Some(
r#"{"version":3,"file":"mod.tsx","sources":["https://deno.land/x/mod.tsx"],"sourcesContent":["{ const foo = \"bar\"; };"],"names":[],"mappings":"AAAA;EAAE,MAAM,MAAM;AAAO"}"#
)
);
}
#[test]
fn test_no_source_map() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.tsx").unwrap();
let source = r#"{ const foo = "bar"; };"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::Tsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let emit_options = EmitOptions {
source_map: SourceMapOption::None,
..Default::default()
};
let emit_result = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&emit_options,
)
.unwrap()
.into_source();
assert_eq!(
&emit_result.text,
r#"{
const foo = "bar";
}"#
);
assert_eq!(emit_result.source_map, None);
}
#[test]
fn test_transpile_owned_when_owned() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"const foo: string = "bar";"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let emit_result = program
.transpile_owned(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions {
source_map: SourceMapOption::None,
..Default::default()
},
)
.unwrap()
.unwrap();
assert_eq!(&emit_result.text, "const foo = \"bar\";\n");
}
#[test]
fn test_transpile_owned_when_cloned() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"const foo: string = "bar";"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let borrowed_program = program.clone();
let result = program.transpile_owned(
&Default::default(),
&Default::default(),
&Default::default(),
);
let program = result.err().unwrap();
drop(borrowed_program);
let borrowed_program = program.program().clone();
let result = program.transpile_owned(
&Default::default(),
&Default::default(),
&Default::default(),
);
let program = result.err().unwrap();
drop(borrowed_program);
let emit_result = program
.transpile_owned(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions {
source_map: SourceMapOption::None,
..Default::default()
},
)
.unwrap()
.unwrap();
assert_eq!(&emit_result.text, "const foo = \"bar\";\n");
}
#[test]
fn test_transpile_owned_with_fallback() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"const foo: string = "bar";"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: false,
})
.unwrap();
let borrowed_program = program.clone();
let emit_result = program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions {
source_map: SourceMapOption::None,
..Default::default()
},
)
.unwrap();
let emit_result = match emit_result {
TranspileResult::Owned(_) => unreachable!(),
TranspileResult::Cloned(emit_result) => emit_result,
};
assert_eq!(&emit_result.text, "const foo = \"bar\";\n");
let emit_result = borrowed_program
.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions {
source_map: SourceMapOption::None,
..Default::default()
},
)
.unwrap();
let emit_result = match emit_result {
TranspileResult::Cloned(_) => unreachable!(),
TranspileResult::Owned(emit_result) => emit_result,
};
assert_eq!(&emit_result.text, "const foo = \"bar\";\n");
}
#[test]
fn should_not_panic_with_scope_analysis() {
let specifier =
ModuleSpecifier::parse("https://deno.land/x/mod.ts").unwrap();
let source = r#"
const inspect: () => void = eval();
export function defaultFormatter(record: Record): string {
for (let i = 0; i < 10; i++) {
inspect(record);
}
}
export function formatter(record: Record) {
}
"#;
let program = parse_program(ParseParams {
specifier,
text: source.into(),
media_type: MediaType::TypeScript,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true,
})
.unwrap();
let emit_result = program.transpile(
&TranspileOptions::default(),
&TranspileModuleOptions::default(),
&EmitOptions::default(),
);
assert!(emit_result.is_ok());
}
}