use crate::ast::modern::Expression;
use oxc_codegen::{Codegen, CodegenOptions};
pub(crate) trait Render {
fn render(&self) -> Option<String>;
}
pub(crate) fn codegen_options() -> CodegenOptions {
CodegenOptions {
single_quote: true,
..CodegenOptions::default()
}
}
pub(crate) fn render<T: Render + ?Sized>(value: &T) -> Option<String> {
value.render()
}
impl Render for Expression {
fn render(&self) -> Option<String> {
let mut rendered = if let Some(expression) = self.oxc_expression() {
let mut codegen = Codegen::new().with_options(codegen_options());
codegen.print_expression(expression);
codegen.into_source_text()
} else if let Some(source) = self.source_snippet() {
source.trim().to_string()
} else {
return None;
};
rendered = strip_ts_non_null_assertions(&rendered);
for _ in 0..self.parens() {
rendered = format!("({rendered})");
}
Some(rendered)
}
}
fn strip_ts_non_null_assertions(text: &str) -> String {
let bytes = text.as_bytes();
let mut result = String::with_capacity(text.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'!' && i > 0 {
let prev = bytes[i - 1];
let next = bytes.get(i + 1).copied();
if next == Some(b'=') {
result.push('!');
}
else if (prev == b')' || prev == b']' || prev.is_ascii_alphanumeric() || prev == b'_' || prev == b'$')
&& (next.is_none() || matches!(next, Some(b';' | b')' | b',' | b'.' | b'[' | b'\n' | b'\r' | b' ' | b'\t')))
{
} else {
result.push('!');
}
} else {
result.push(bytes[i] as char);
}
i += 1;
}
result
}