Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 82 additions & 49 deletions apps/oxfmt/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use oxc_allocator::AllocatorPool;
use oxc_diagnostics::{DiagnosticSender, DiagnosticService, OxcDiagnostic};
use oxc_formatter::{FormatOptions, Formatter, enable_jsx_source_type, get_parse_options};
use oxc_parser::Parser;
use oxc_span::SourceType;

use crate::{command::OutputOptions, walk::WalkEntry};

Expand Down Expand Up @@ -62,6 +63,12 @@ impl FormatService {
tx_count: mpsc::Sender<()>,
) {
rx_entry.into_iter().par_bridge().for_each(|entry| {
// TODO: For now, just ignore it
// #[not(feature = "napi")]
if matches!(entry, WalkEntry::Prettier { .. }) {
return;
}

self.process_entry(&entry, tx_error, tx_path);
// Signal that we processed one file (ignore send errors if receiver dropped)
let _ = tx_count.send(());
Expand All @@ -72,10 +79,9 @@ impl FormatService {
fn process_entry(&self, entry: &WalkEntry, tx_error: &DiagnosticSender, tx_path: &PathSender) {
let start_time = Instant::now();

let path = &entry.path;
let source_type = enable_jsx_source_type(entry.source_type);

let allocator = self.allocator_pool.get();
let path = match entry {
WalkEntry::OxcFormatter { path, .. } | WalkEntry::Prettier { path } => path,
};
let Ok(source_text) = read_to_string(path) else {
// This happens if `.ts` for MPEG-TS binary is attempted to be formatted
let diagnostics = DiagnosticService::wrap_diagnostics(
Expand All @@ -91,63 +97,26 @@ impl FormatService {
return;
};

let ret = Parser::new(&allocator, &source_text, source_type)
.with_options(get_parse_options())
.parse();
if !ret.errors.is_empty() {
let diagnostics = DiagnosticService::wrap_diagnostics(
self.cwd.clone(),
path,
&source_text,
ret.errors,
);
tx_error.send(diagnostics).unwrap();
return;
}

let base_formatter = Formatter::new(&allocator, self.format_options.clone());

#[cfg(feature = "napi")]
let formatted = {
if self.format_options.embedded_language_formatting.is_off() {
base_formatter.format(&ret.program)
} else {
let embedded_formatter = self
.external_formatter
.as_ref()
.expect("`external_formatter` must exist when `napi` feature is enabled")
.to_embedded_formatter();
base_formatter.format_with_embedded(&ret.program, embedded_formatter)
let format_result = match entry {
WalkEntry::OxcFormatter { path, source_type } => {
self.format_by_oxc_formatter(path, &source_text, *source_type)
}
WalkEntry::Prettier { path } => self.format_by_prettier(path, &source_text),
};
#[cfg(not(feature = "napi"))]
let formatted = base_formatter.format(&ret.program);

let code = match formatted.print() {
Ok(printed) => printed.into_code(),
Err(err) => {
let code = match format_result {
Ok(code) => code,
Err(diagnostics) => {
let diagnostics = DiagnosticService::wrap_diagnostics(
self.cwd.clone(),
path,
&source_text,
vec![OxcDiagnostic::error(format!(
"Failed to print formatted code: {}\n{err}",
path.display()
))],
diagnostics,
);
tx_error.send(diagnostics).unwrap();
return;
}
};

#[cfg(feature = "detect_code_removal")]
{
if let Some(diff) = oxc_formatter::detect_code_removal(&source_text, &code, source_type)
{
unreachable!("Code removal detected in `{}`:\n{diff}", path.to_string_lossy());
}
}

let elapsed = start_time.elapsed();
let is_changed = source_text != code;

Expand Down Expand Up @@ -182,6 +151,70 @@ impl FormatService {
tx_path.send(output).unwrap();
}
}

fn format_by_oxc_formatter(
&self,
path: &Path,
source_text: &str,
source_type: SourceType,
) -> Result<String, Vec<OxcDiagnostic>> {
let allocator = self.allocator_pool.get();
let source_type = enable_jsx_source_type(source_type);

let ret = Parser::new(&allocator, source_text, source_type)
.with_options(get_parse_options())
.parse();
if !ret.errors.is_empty() {
return Err(ret.errors);
}

let base_formatter = Formatter::new(&allocator, self.format_options.clone());

#[cfg(feature = "napi")]
let formatted = {
if self.format_options.embedded_language_formatting.is_off() {
base_formatter.format(&ret.program)
} else {
let embedded_formatter = self
.external_formatter
.as_ref()
.expect("`external_formatter` must exist when `napi` feature is enabled")
.to_embedded_formatter();
base_formatter.format_with_embedded(&ret.program, embedded_formatter)
}
};
#[cfg(not(feature = "napi"))]
let formatted = base_formatter.format(&ret.program);

let code = match formatted.print() {
Ok(printed) => printed.into_code(),
Err(err) => {
return Err(vec![OxcDiagnostic::error(format!(
"Failed to print formatted code: {}\n{err}",
path.display()
))]);
}
};

#[cfg(feature = "detect_code_removal")]
{
if let Some(diff) = oxc_formatter::detect_code_removal(source_text, &code, source_type)
{
unreachable!("Code removal detected in `{}`:\n{diff}", path.to_string_lossy());
}
}

Ok(code)
}

#[expect(clippy::unused_self)]
fn format_by_prettier(
&self,
_path: &Path,
_source_text: &str,
) -> Result<String, Vec<OxcDiagnostic>> {
unreachable!("Prettier formatting is not implemented yet");
}
}

fn read_to_string(path: &Path) -> io::Result<String> {
Expand Down
18 changes: 11 additions & 7 deletions apps/oxfmt/src/walk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@ fn load_ignore_paths(cwd: &Path, ignore_paths: &[PathBuf]) -> Vec<PathBuf> {

// ---

pub struct WalkEntry {
pub path: PathBuf,
pub source_type: SourceType,
pub enum WalkEntry {
OxcFormatter { path: PathBuf, source_type: SourceType },
Prettier { path: PathBuf },
}

struct WalkBuilder {
Expand All @@ -229,10 +229,14 @@ impl ignore::ParallelVisitor for WalkVisitor {

// Use `is_file()` to detect symlinks to the directory named `.js`
#[expect(clippy::filetype_is_file)]
if file_type.is_file()
&& let Some(source_type) = get_supported_source_type(entry.path())
{
let walk_entry = WalkEntry { path: entry.path().to_path_buf(), source_type };
if file_type.is_file() {
let path = entry.path().to_path_buf();
let walk_entry = if let Some(source_type) = get_supported_source_type(&path) {
WalkEntry::OxcFormatter { path, source_type }
} else {
WalkEntry::Prettier { path }
};

// Send each entry immediately through the channel
// If send fails, the receiver has been dropped, so stop walking
if self.sender.send(walk_entry).is_err() {
Expand Down
Loading