use crate::host::error::Result;
use crate::ts_syn::abi::{Patch, PatchCode};
use std::collections::HashSet;
#[cfg(feature = "swc")]
use swc_core::{
common::{SourceMap, sync::Lrc},
ecma::codegen::{Config, Emitter, Node, text_writer::JsWriter},
};
pub(crate) fn dedupe_patches(patches: &mut Vec<Patch>) -> Result<()> {
dedupe_imports(patches);
let mut seen: HashSet<(u8, u32, u32, Option<String>)> = HashSet::new();
let mut indices_to_keep = Vec::new();
for (i, patch) in patches.iter().enumerate() {
let key = match patch {
Patch::Insert { at, code, .. } => (0, at.start, at.end, Some(render_patch_code(code)?)),
Patch::InsertRaw { at, code, .. } => (3, at.start, at.end, Some(code.clone())),
Patch::Replace { span, code, .. } => {
(1, span.start, span.end, Some(render_patch_code(code)?))
}
Patch::ReplaceRaw { span, code, .. } => (4, span.start, span.end, Some(code.clone())),
Patch::Delete { span } => (2, span.start, span.end, None),
};
if seen.insert(key) {
indices_to_keep.push(i);
}
}
let old_patches = std::mem::take(patches);
*patches = indices_to_keep
.into_iter()
.map(|i| old_patches[i].clone())
.collect();
Ok(())
}
pub(crate) fn parse_import_patch(code: &str) -> Option<(String, String, bool)> {
let trimmed = code.trim();
let is_type = trimmed.starts_with("import type ");
let rest = if is_type {
trimmed.strip_prefix("import type ")?
} else {
trimmed.strip_prefix("import ")?
};
let brace_start = rest.find('{')?;
let brace_end = rest.find('}')?;
let specifier_raw = rest[brace_start + 1..brace_end].trim();
let base_specifier = if let Some(pos) = specifier_raw.find(" as ") {
specifier_raw[..pos].trim().to_string()
} else {
specifier_raw.to_string()
};
let after_brace = &rest[brace_end + 1..];
let quote_char = if after_brace.contains('"') { '"' } else { '\'' };
let first_quote = after_brace.find(quote_char)?;
let second_quote = after_brace[first_quote + 1..].find(quote_char)?;
let module = after_brace[first_quote + 1..first_quote + 1 + second_quote].to_string();
Some((base_specifier, module, is_type))
}
pub(crate) fn dedupe_imports(patches: &mut Vec<Patch>) {
let mut value_imports: HashSet<(String, String)> = HashSet::new();
for patch in patches.iter() {
if let Patch::InsertRaw {
context: Some(ctx),
code,
..
} = patch
&& ctx == "import"
&& let Some((specifier, module, is_type)) = parse_import_patch(code)
&& !is_type
{
value_imports.insert((specifier, module));
}
}
if value_imports.is_empty() {
return;
}
patches.retain(|patch| {
if let Patch::InsertRaw {
context: Some(ctx),
code,
..
} = patch
&& ctx == "import"
&& let Some((specifier, module, is_type)) = parse_import_patch(code)
&& is_type
&& value_imports.contains(&(specifier, module))
{
return false; }
true
});
}
pub(crate) fn render_patch_code(code: &PatchCode) -> Result<String> {
match code {
PatchCode::Text(s) => Ok(s.clone()),
#[cfg(feature = "swc")]
PatchCode::ClassMember(member) => emit_node(member),
#[cfg(feature = "swc")]
PatchCode::Stmt(stmt) => emit_node(stmt),
#[cfg(feature = "swc")]
PatchCode::ModuleItem(item) => emit_node(item),
}
}
#[cfg(feature = "swc")]
pub(crate) fn emit_node<N: Node>(node: &N) -> Result<String> {
let cm: Lrc<SourceMap> = Default::default();
let mut buf = Vec::new();
{
let writer = JsWriter::new(cm.clone(), "\n", &mut buf, None);
let mut emitter = Emitter {
cfg: Config::default(),
cm: cm.clone(),
comments: None,
wr: writer,
};
node.emit_with(&mut emitter)
.map_err(|err| anyhow::anyhow!(err))?;
}
let output = String::from_utf8(buf).map_err(|err| anyhow::anyhow!(err))?;
Ok(output.trim_end().to_string())
}