use std::string::FromUtf8Error;
use anyhow::Result;
use base64::Engine;
use thiserror::Error;
use crate::swc::ast::Program;
use crate::swc::codegen::text_writer::JsWriter;
use crate::swc::codegen::Node;
use crate::swc::common::FileName;
use crate::SourceMap;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SourceMapOption {
#[default]
Inline,
Separate,
None,
}
#[derive(Debug, Clone, Hash)]
pub struct EmitOptions {
pub source_map: SourceMapOption,
pub source_map_file: Option<String>,
pub inline_sources: bool,
pub remove_comments: bool,
}
impl Default for EmitOptions {
fn default() -> Self {
EmitOptions {
source_map: SourceMapOption::default(),
source_map_file: None,
inline_sources: true,
remove_comments: false,
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)]
pub struct EmittedSourceBytes {
pub source: Vec<u8>,
pub source_map: Option<Vec<u8>>,
}
impl EmittedSourceBytes {
pub fn into_string(self) -> Result<EmittedSourceText, FromUtf8Error> {
let text = String::from_utf8(self.source)?;
let source_map = self.source_map.map(String::from_utf8).transpose()?;
Ok(EmittedSourceText { text, source_map })
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)]
pub struct EmittedSourceText {
pub text: String,
pub source_map: Option<String>,
}
#[derive(Debug, Error)]
pub enum EmitError {
#[error(transparent)]
SwcEmit(anyhow::Error),
#[error(transparent)]
SourceMap(anyhow::Error),
}
pub fn emit(
program: &Program,
comments: &dyn crate::swc::common::comments::Comments,
source_map: &SourceMap,
emit_options: &EmitOptions,
) -> Result<EmittedSourceBytes, EmitError> {
let source_map = source_map.inner();
let mut src_map_buf = vec![];
let mut src_buf = vec![];
{
let mut writer = Box::new(JsWriter::new(
source_map.clone(),
"\n",
&mut src_buf,
Some(&mut src_map_buf),
));
writer.set_indent_str(" "); let mut emitter = crate::swc::codegen::Emitter {
cfg: swc_codegen_config(),
comments: if emit_options.remove_comments {
None
} else {
Some(&comments)
},
cm: source_map.clone(),
wr: writer,
};
program
.emit_with(&mut emitter)
.map_err(|e| EmitError::SwcEmit(e.into()))?;
}
let mut map: Option<Vec<u8>> = None;
if emit_options.source_map != SourceMapOption::None {
let mut map_buf = Vec::new();
let source_map_config = SourceMapConfig {
inline_sources: emit_options.inline_sources,
};
let mut source_map = source_map.build_source_map_with_config(
&src_map_buf,
None,
source_map_config,
);
if let Some(file) = &emit_options.source_map_file {
source_map.set_file(Some(file.to_string()));
}
source_map
.to_writer(&mut map_buf)
.map_err(|e| EmitError::SourceMap(e.into()))?;
if emit_options.source_map == SourceMapOption::Inline {
let mut inline_buf = vec![0; map_buf.len() * 4 / 3 + 4];
let size = base64::prelude::BASE64_STANDARD
.encode_slice(map_buf, &mut inline_buf)
.map_err(|err| EmitError::SourceMap(err.into()))?;
let inline_buf = &inline_buf[..size];
let prelude_text = "//# sourceMappingURL=data:application/json;base64,";
let src_has_trailing_newline = src_buf.ends_with(&[b'\n']);
let additional_capacity = if src_has_trailing_newline { 0 } else { 1 }
+ prelude_text.len()
+ inline_buf.len();
let expected_final_capacity = src_buf.len() + additional_capacity;
src_buf.reserve(additional_capacity);
if !src_has_trailing_newline {
src_buf.push(b'\n');
}
src_buf.extend(prelude_text.as_bytes());
src_buf.extend(inline_buf);
debug_assert_eq!(src_buf.len(), expected_final_capacity);
} else {
map = Some(map_buf);
}
}
Ok(EmittedSourceBytes {
source: src_buf,
source_map: map,
})
}
#[derive(Debug)]
pub struct SourceMapConfig {
pub inline_sources: bool,
}
impl crate::swc::common::source_map::SourceMapGenConfig for SourceMapConfig {
fn file_name_to_source(&self, f: &FileName) -> String {
f.to_string()
}
fn inline_sources_content(&self, f: &FileName) -> bool {
match f {
FileName::Real(..) | FileName::Custom(..) => false,
FileName::Url(..) => self.inline_sources,
_ => true,
}
}
}
pub fn swc_codegen_config() -> crate::swc::codegen::Config {
let mut config = crate::swc::codegen::Config::default();
config.minify = false;
config.ascii_only = false;
config.omit_last_semi = false;
config.target = crate::ES_VERSION;
config.emit_assert_for_import_attributes = false;
config
}