Skip to content

Commit f0fa410

Browse files
committed
Minimal prototype using regex
1 parent 06440dc commit f0fa410

2 files changed

Lines changed: 62 additions & 2 deletions

File tree

crates/ruff/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ ruff_options_metadata = { workspace = true, features = ["serde"] }
3131
ruff_python_ast = { workspace = true }
3232
ruff_python_formatter = { workspace = true }
3333
ruff_python_parser = { workspace = true }
34+
ruff_python_trivia = { workspace = true }
3435
ruff_server = { workspace = true }
3536
ruff_source_file = { workspace = true }
3637
ruff_text_size = { workspace = true }

crates/ruff/src/commands/format.rs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ use itertools::Itertools;
1111
use log::{error, warn};
1212
use rayon::iter::Either::{Left, Right};
1313
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
14+
use regex::{Captures, Regex};
1415
use ruff_db::diagnostic::{
1516
Annotation, Diagnostic, DiagnosticId, DisplayDiagnosticConfig, Severity, Span,
1617
};
1718
use ruff_linter::message::{EmitterContext, create_panic_diagnostic, render_diagnostics};
1819
use ruff_linter::settings::types::OutputFormat;
1920
use ruff_notebook::NotebookIndex;
2021
use ruff_python_parser::ParseError;
22+
use ruff_python_trivia::textwrap::{dedent, indent};
2123
use rustc_hash::{FxHashMap, FxHashSet};
2224
use thiserror::Error;
2325
use tracing::debug;
@@ -489,8 +491,65 @@ pub(crate) fn format_source(
489491
formatted,
490492
)))
491493
}
492-
SourceKind::Markdown(source) => {
493-
unimplemented!()
494+
SourceKind::Markdown(unformatted_document) => {
495+
// adapted from blacken-docs
496+
// https://github.com/adamchainz/blacken-docs/blob/fb107c1dce25f9206e29297aaa1ed7afc2980a5a/src/blacken_docs/__init__.py#L17
497+
let code_block_regex = Regex::new(
498+
r"(?imsx)
499+
(?<before>
500+
^(?<indent>\ *)```[^\S\r\n]*
501+
(?:python|py|python3|py3)
502+
(?:\ .*?)?\n
503+
)
504+
(?<code>.*?)
505+
(?<after>
506+
^\ *```[^\S\r\n]*$
507+
)
508+
",
509+
)
510+
.unwrap();
511+
512+
let mut changed = false;
513+
let formatted_document =
514+
code_block_regex.replace_all(unformatted_document, |capture: &Captures| {
515+
let (original, [before, code_indent, unformatted_code, after]) =
516+
capture.extract();
517+
518+
let unformatted_code = dedent(unformatted_code);
519+
let options = settings.to_format_options(source_type, &unformatted_code, path);
520+
521+
let formatted_code = if let Some(_range) = range {
522+
unimplemented!()
523+
} else {
524+
// Using `Printed::into_code` requires adding `ruff_formatter` as a direct dependency, and I suspect that Rust can optimize the closure away regardless.
525+
#[expect(clippy::redundant_closure_for_method_calls)]
526+
format_module_source(&unformatted_code, options)
527+
.map(|formatted| formatted.into_code())
528+
};
529+
530+
// TODO: figure out how to properly raise errors from inside closure
531+
if let Ok(formatted_code) = formatted_code {
532+
if formatted_code.len() == unformatted_code.len()
533+
&& formatted_code == *unformatted_code
534+
{
535+
original.to_string()
536+
} else {
537+
changed = true;
538+
let formatted_code = indent(formatted_code.as_str(), code_indent);
539+
format!("{before}{formatted_code}{after}")
540+
}
541+
} else {
542+
original.to_string()
543+
}
544+
});
545+
546+
if changed {
547+
Ok(FormattedSource::Formatted(SourceKind::Markdown(
548+
formatted_document.to_string(),
549+
)))
550+
} else {
551+
Ok(FormattedSource::Unchanged)
552+
}
494553
}
495554
}
496555
}

0 commit comments

Comments
 (0)