use crate::error::FormatError;
use crate::options::FormatOptions;
use memchr::memchr;
use oxc_allocator::Allocator as OxcAllocator;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_parser::Parser;
use oxc_span::SourceType;
use vize_carton::Allocator;
#[inline]
pub fn format_script_content(
source: &str,
options: &FormatOptions,
_allocator: &Allocator,
) -> Result<String, FormatError> {
let trimmed = source.trim();
if trimmed.is_empty() {
return Ok(String::new());
}
let oxc_allocator = OxcAllocator::default();
let source_type = SourceType::ts().with_module(true);
let parser = Parser::new(&oxc_allocator, source, source_type);
let parsed = parser.parse();
if !parsed.errors.is_empty() {
let error_messages: Vec<String> = parsed.errors.iter().map(|e| e.to_string()).collect();
return Err(FormatError::ScriptParseError(error_messages.join("; ")));
}
let codegen_options = convert_to_codegen_options(options);
let codegen = Codegen::new().with_options(codegen_options);
let formatted = codegen.build(&parsed.program).code;
let formatted = post_process_script_fast(&formatted, options);
Ok(formatted)
}
#[inline]
fn convert_to_codegen_options(options: &FormatOptions) -> CodegenOptions {
CodegenOptions {
single_quote: options.single_quote,
minify: false,
comments: true,
annotation_comments: true,
source_map_path: None,
..Default::default()
}
}
#[inline]
fn post_process_script_fast(source: &str, options: &FormatOptions) -> String {
let bytes = source.as_bytes();
let len = bytes.len();
let mut result = Vec::with_capacity(len + len / 4);
let newline = options.newline_bytes();
let tab_width = options.tab_width as usize;
let use_tabs = options.use_tabs;
let mut pos = 0;
while pos < len {
let mut leading_spaces = 0;
while pos < len {
match bytes[pos] {
b' ' => {
leading_spaces += 1;
pos += 1;
}
b'\t' => {
leading_spaces += tab_width;
pos += 1;
}
_ => break,
}
}
let line_end = if let Some(newline_pos) = memchr(b'\n', &bytes[pos..]) {
pos + newline_pos
} else {
len
};
let indent_level = leading_spaces / 2;
if use_tabs {
result.extend(std::iter::repeat_n(b'\t', indent_level));
} else {
result.extend(std::iter::repeat_n(b' ', indent_level * tab_width));
}
let content_end = if line_end > 0 && bytes[line_end - 1] == b'\r' {
line_end - 1
} else {
line_end
};
if pos < content_end {
result.extend_from_slice(&bytes[pos..content_end]);
}
result.extend_from_slice(newline);
pos = if line_end < len { line_end + 1 } else { len };
}
let result = if !options.semi {
remove_optional_semicolons_fast(&result, newline)
} else {
result
};
let result = if options.bracket_spacing {
ensure_bracket_spacing_fast(&result)
} else {
result
};
let mut final_result = trim_trailing_whitespace(&result);
final_result.extend_from_slice(newline);
unsafe { String::from_utf8_unchecked(final_result) }
}
#[inline]
fn remove_optional_semicolons_fast(source: &[u8], _newline: &[u8]) -> Vec<u8> {
let mut result = Vec::with_capacity(source.len());
let mut i = 0;
let len = source.len();
while i < len {
let b = source[i];
if b == b'"' || b == b'\'' || b == b'`' {
let quote = b;
result.push(b);
i += 1;
while i < len {
let c = source[i];
result.push(c);
i += 1;
if c == quote && (i < 2 || source[i - 2] != b'\\') {
break;
}
}
continue;
}
if b == b';' {
let next_idx = i + 1;
if next_idx >= len
|| source[next_idx] == b'\n'
|| (next_idx + 1 < len
&& source[next_idx] == b'\r'
&& source[next_idx + 1] == b'\n')
{
i += 1;
continue;
}
}
result.push(b);
i += 1;
}
result
}
#[inline]
fn ensure_bracket_spacing_fast(source: &[u8]) -> Vec<u8> {
let mut result = Vec::with_capacity(source.len() + source.len() / 10);
let mut i = 0;
let len = source.len();
while i < len {
let b = source[i];
if b == b'"' || b == b'\'' || b == b'`' {
let quote = b;
result.push(b);
i += 1;
while i < len {
let c = source[i];
result.push(c);
i += 1;
if c == quote && (i < 2 || source[i - 2] != b'\\') {
break;
}
}
continue;
}
if b == b'{' {
result.push(b);
i += 1;
if i < len {
let next = source[i];
if next != b'}' && next != b' ' && next != b'\n' && next != b'\r' {
result.push(b' ');
}
}
continue;
}
if b == b'}' {
if !result.is_empty() {
let last = *result.last().unwrap();
if last != b'{' && last != b' ' && last != b'\n' && last != b'\r' {
result.push(b' ');
}
}
result.push(b);
i += 1;
continue;
}
result.push(b);
i += 1;
}
result
}
#[inline]
fn trim_trailing_whitespace(source: &[u8]) -> Vec<u8> {
let mut result = Vec::with_capacity(source.len());
let mut line_start = 0;
for (i, &b) in source.iter().enumerate() {
if b == b'\n' {
let mut line_end = i;
while line_end > line_start {
let c = source[line_end - 1];
if c != b' ' && c != b'\t' && c != b'\r' {
break;
}
line_end -= 1;
}
result.extend_from_slice(&source[line_start..line_end]);
result.push(b'\n');
line_start = i + 1;
}
}
if line_start < source.len() {
let mut line_end = source.len();
while line_end > line_start {
let c = source[line_end - 1];
if c != b' ' && c != b'\t' && c != b'\r' && c != b'\n' {
break;
}
line_end -= 1;
}
result.extend_from_slice(&source[line_start..line_end]);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_simple_script() {
let source = "const x=1";
let options = FormatOptions::default();
let allocator = Allocator::default();
let result = format_script_content(source, &options, &allocator).unwrap();
assert!(result.contains("const x = 1"));
}
#[test]
fn test_format_with_imports() {
let source = "import {ref,computed} from 'vue'";
let options = FormatOptions::default();
let allocator = Allocator::default();
let result = format_script_content(source, &options, &allocator).unwrap();
assert!(result.contains("ref"));
assert!(result.contains("computed"));
assert!(result.contains("vue"));
}
#[test]
fn test_format_object() {
let source = "const obj={a:1,b:2}";
let options = FormatOptions::default();
let allocator = Allocator::default();
let result = format_script_content(source, &options, &allocator).unwrap();
assert!(result.contains("a:"));
assert!(result.contains("b:"));
}
#[test]
fn test_format_empty_source() {
let source = "";
let options = FormatOptions::default();
let allocator = Allocator::default();
let result = format_script_content(source, &options, &allocator).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_format_whitespace_only() {
let source = " \n\t ";
let options = FormatOptions::default();
let allocator = Allocator::default();
let result = format_script_content(source, &options, &allocator).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_bracket_spacing() {
let input = b"const x = {a: 1}";
let result = ensure_bracket_spacing_fast(input);
let result_str = std::str::from_utf8(&result).unwrap();
assert!(result_str.contains("{ a: 1 }"));
}
}