use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::compiler::compile_template;
pub(crate) fn convert_doc_attributes_to_jsdoc(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let chars: Vec<char> = input.chars().collect();
let len = chars.len();
let mut i = 0;
#[cfg(debug_assertions)]
let debug = std::env::var("MF_DEBUG_TEMPLATE").is_ok();
while i < len {
if i + 7 < len && chars[i] == '#' {
#[cfg(debug_assertions)]
if debug {
let context: String = chars[i..std::cmp::min(i + 20, len)].iter().collect();
eprintln!(
"[MF_DEBUG_DOC] Found # at pos {}, context: {:?}",
i, context
);
}
let mut j = i + 1;
while j < len && chars[j].is_whitespace() {
j += 1;
}
if j < len && chars[j] == '[' {
j += 1;
while j < len && chars[j].is_whitespace() {
j += 1;
}
if j + 3 <= len && chars[j] == 'd' && chars[j + 1] == 'o' && chars[j + 2] == 'c' {
j += 3;
while j < len && chars[j].is_whitespace() {
j += 1;
}
if j < len && chars[j] == '=' {
j += 1;
while j < len && chars[j].is_whitespace() {
j += 1;
}
if j < len && chars[j] == 'r' {
j += 1;
}
if j < len && chars[j] == '"' {
j += 1;
let doc_start = j;
while j < len && chars[j] != '"' {
j += 1;
}
let doc_end = j;
if j < len && chars[j] == '"' {
j += 1;
while j < len && chars[j].is_whitespace() {
j += 1;
}
if j < len && chars[j] == ']' {
j += 1;
let doc_text: String =
chars[doc_start..doc_end].iter().collect();
let mut doc_text = doc_text.trim().to_string();
if let Some(stripped) = doc_text.strip_prefix('*') {
doc_text = stripped.trim_start().to_string();
}
if let Some(stripped) = doc_text.strip_suffix("*/") {
doc_text = stripped.trim_end().to_string();
}
result.push_str("/** ");
result.push_str(&doc_text);
result.push_str(" */");
i = j;
continue;
}
}
}
}
}
}
}
result.push(chars[i]);
i += 1;
}
result
}
pub(crate) fn normalize_template_spacing(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let chars: Vec<char> = input.chars().collect();
let len = chars.len();
let mut i = 0;
while i < len {
let c = chars[i];
if c == '='
&& i + 4 < len
&& chars[i + 1] == ' '
&& chars[i + 2] == '='
&& chars[i + 3] == ' '
&& chars[i + 4] == '='
{
result.push_str("===");
i += 5;
continue;
}
if c == '!'
&& i + 4 < len
&& chars[i + 1] == ' '
&& chars[i + 2] == '='
&& chars[i + 3] == ' '
&& chars[i + 4] == '='
{
result.push_str("!==");
i += 5;
continue;
}
if c == '!' && i + 2 < len && chars[i + 1] == ' ' && chars[i + 2] == '=' {
if !(i + 4 < len && chars[i + 3] == ' ' && chars[i + 4] == '=') {
result.push_str("!=");
i += 3;
continue;
}
}
if c == '=' && i + 2 < len && chars[i + 1] == ' ' && chars[i + 2] == '=' {
if i + 4 < len && chars[i + 3] == ' ' && chars[i + 4] == '=' {
} else {
result.push_str("==");
i += 3;
continue;
}
}
if c == '&' && i + 2 < len && chars[i + 1] == ' ' && chars[i + 2] == '&' {
result.push_str("&&");
i += 3;
continue;
}
if c == '|' && i + 2 < len && chars[i + 1] == ' ' && chars[i + 2] == '|' {
result.push_str("||");
i += 3;
continue;
}
if c == ':' && i + 2 < len && chars[i + 1] == ' ' && chars[i + 2] == ':' {
result.push_str("::");
i += 3;
continue;
}
if c == '=' && i + 2 < len && chars[i + 1] == ' ' && chars[i + 2] == '>' {
result.push_str("=>");
i += 3;
continue;
}
if c == '<' && i + 2 < len && chars[i + 1] == ' ' && chars[i + 2] == '=' {
result.push_str("<=");
i += 3;
continue;
}
if c == '>' && i + 2 < len && chars[i + 1] == ' ' && chars[i + 2] == '=' {
result.push_str(">=");
i += 3;
continue;
}
if c == '+' && i + 2 < len && chars[i + 1] == ' ' && chars[i + 2] == '=' {
result.push_str("+=");
i += 3;
continue;
}
if c == '-' && i + 2 < len && chars[i + 1] == ' ' && chars[i + 2] == '=' {
result.push_str("-=");
i += 3;
continue;
}
if c == '-' && i + 2 < len && chars[i + 1] == ' ' && chars[i + 2] == '>' {
result.push_str("->");
i += 3;
continue;
}
if c == '.'
&& i + 4 < len
&& chars[i + 1] == ' '
&& chars[i + 2] == '.'
&& chars[i + 3] == ' '
&& chars[i + 4] == '.'
{
result.push_str("...");
i += 5;
continue;
}
if c == '*' && i + 1 < len && chars[i + 1] == '*' {
let mut j = i;
while j > 0 {
let prev = chars[j - 1];
if prev == '\n' || prev == '\r' {
break;
}
if prev.is_whitespace() {
j -= 1;
continue;
}
break;
}
if j == 0 || chars[j.saturating_sub(1)] == '\n' || chars[j.saturating_sub(1)] == '\r' {
result.push_str("/**");
i += 2;
continue;
}
}
if c == '@' {
let mut peek = i + 1;
while peek < len && chars[peek].is_whitespace() {
peek += 1;
}
if peek < len && (chars[peek] == '{' || chars[peek] == '@') {
result.push('@');
i = peek; continue;
}
result.push('@');
i += 1;
}
else if c == '{' {
let start = i;
i += 1;
while i < len && chars[i].is_whitespace() {
i += 1;
}
if i < len
&& (chars[i] == '#'
|| chars[i] == '/'
|| chars[i] == ':'
|| chars[i] == '$'
|| chars[i] == '|')
{
result.push('{');
continue;
} else {
result.push('{');
for &ch in chars.iter().take(i).skip(start + 1) {
result.push(ch);
}
continue;
}
}
else if c == '|' {
result.push('|');
i += 1;
let ws_start = i;
while i < len && chars[i].is_whitespace() {
i += 1;
}
if i < len && chars[i] == '}' {
continue;
} else {
for &ch in chars.iter().take(i).skip(ws_start) {
result.push(ch);
}
continue;
}
} else {
result.push(c);
i += 1;
}
}
result
}
pub(crate) fn collapse_template_newlines(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let chars: Vec<char> = input.chars().collect();
let len = chars.len();
let mut i = 0;
let mut in_string = false;
let mut string_char = '\0';
let mut escape_next = false;
let mut in_line_comment = false;
let mut in_block_comment = false;
while i < len {
let ch = chars[i];
if in_line_comment {
result.push(ch);
if ch == '\n' {
in_line_comment = false;
}
i += 1;
continue;
}
if in_block_comment {
result.push(ch);
if ch == '*' && i + 1 < len && chars[i + 1] == '/' {
result.push('/');
i += 2;
in_block_comment = false;
continue;
}
i += 1;
continue;
}
if in_string {
result.push(ch);
if escape_next {
escape_next = false;
} else if ch == '\\' {
escape_next = true;
} else if ch == string_char {
in_string = false;
}
i += 1;
continue;
}
if ch == '/' && i + 1 < len {
if chars[i + 1] == '/' {
in_line_comment = true;
result.push(ch);
result.push('/');
i += 2;
continue;
}
if chars[i + 1] == '*' {
in_block_comment = true;
result.push(ch);
result.push('*');
i += 2;
continue;
}
}
if ch == '\n' || ch == '\r' {
if !result.ends_with(' ') {
result.push(' ');
}
i += 1;
continue;
}
if ch == '"' || ch == '\'' || ch == '`' {
in_string = true;
string_char = ch;
}
result.push(ch);
i += 1;
}
result
}
pub fn parse_template(input: TokenStream2) -> syn::Result<TokenStream2> {
let template_str = input.to_string();
#[cfg(debug_assertions)]
if std::env::var("MF_DEBUG_TEMPLATE").is_ok() {
eprintln!(
"[MF_DEBUG] Raw tokenized ({} chars): {:?}",
template_str.len(),
template_str
);
}
let template_str = convert_doc_attributes_to_jsdoc(&template_str);
#[cfg(debug_assertions)]
if std::env::var("MF_DEBUG_TEMPLATE").is_ok() {
eprintln!(
"[MF_DEBUG] After doc conversion ({} chars): {:?}",
template_str.len(),
template_str
);
}
let template_str = normalize_template_spacing(&template_str);
let template_str = collapse_template_newlines(&template_str);
#[cfg(debug_assertions)]
if std::env::var("MF_DEBUG_TEMPLATE").is_ok() {
eprintln!(
"[MF_DEBUG] After normalization ({} chars): {:?}",
template_str.len(),
template_str
);
}
let stmts_builder = compile_template(&template_str, "__stmts")?;
Ok(quote! {
{
let mut __stmts: Vec<macroforge_ts::swc_core::ecma::ast::ModuleItem> = Vec::new();
let mut __patches: Vec<macroforge_ts::ts_syn::abi::Patch> = Vec::new();
let __comments = macroforge_ts::swc_core::common::comments::SingleThreadedComments::default();
let mut __pending_comments: Vec<macroforge_ts::swc_core::common::comments::Comment> = Vec::new();
let mut __injected_streams: Vec<macroforge_ts::ts_syn::TsStream> = Vec::new();
let __mf_items: Vec<macroforge_ts::swc_core::ecma::ast::ModuleItem> = #stmts_builder;
__stmts.extend(__mf_items);
(__stmts, __patches, __comments, __injected_streams)
}
})
}
pub(crate) fn strip_doc_comments(input: &str) -> String {
let bytes = input.as_bytes();
let mut result = String::with_capacity(input.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i..].starts_with(b"/**")
&& let Some(end) = input[i + 3..].find("*/")
{
i += 3 + end + 2;
continue;
}
if bytes[i..].starts_with(b"///") {
if let Some(end) = input[i + 3..].find('\n') {
i += 3 + end + 1;
continue;
} else {
break;
}
}
result.push(bytes[i] as char);
i += 1;
}
result
}
pub(crate) fn parse_template_str(template_str: &str) -> syn::Result<TokenStream2> {
let stmts_builder = compile_template(template_str, "__stmts")?;
Ok(quote! {
{
let mut __stmts: Vec<macroforge_ts::swc_core::ecma::ast::ModuleItem> = Vec::new();
let mut __patches: Vec<macroforge_ts::ts_syn::abi::Patch> = Vec::new();
let __comments = macroforge_ts::swc_core::common::comments::SingleThreadedComments::default();
let mut __pending_comments: Vec<macroforge_ts::swc_core::common::comments::Comment> = Vec::new();
let mut __injected_streams: Vec<macroforge_ts::ts_syn::TsStream> = Vec::new();
let __mf_items: Vec<macroforge_ts::swc_core::ecma::ast::ModuleItem> = #stmts_builder;
__stmts.extend(__mf_items);
(__stmts, __patches, __comments, __injected_streams)
}
})
}