Skip to content

Commit 9e35f1a

Browse files
authored
feat: allow external formatter to cause formatting error (#710)
1 parent 6165957 commit 9e35f1a

File tree

4 files changed

+61
-24
lines changed

4 files changed

+61
-24
lines changed

src/format_text.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,20 @@ pub fn format_parsed_source(source: &ParsedSource, config: &Configuration, exter
9393
}
9494

9595
fn inner_format(parsed_source: &ParsedSource, config: &Configuration, external_formatter: Option<&ExternalFormatter>) -> Result<Option<String>> {
96+
let mut maybe_err: Box<Option<anyhow::Error>> = Box::new(None);
9697
let result = dprint_core::formatting::format(
97-
|| {
98-
#[allow(clippy::let_and_return)]
99-
let print_items = generate(parsed_source, config, external_formatter);
100-
// println!("{}", print_items.get_as_text());
101-
print_items
98+
|| match generate(parsed_source, config, external_formatter) {
99+
Ok(print_items) => print_items,
100+
Err(e) => {
101+
maybe_err.replace(e);
102+
PrintItems::default()
103+
}
102104
},
103105
config_to_print_options(parsed_source.text(), config),
104106
);
107+
if let Some(e) = maybe_err.take() {
108+
return Err(e);
109+
}
105110
if result == parsed_source.text().as_ref() {
106111
Ok(None)
107112
} else {
@@ -145,4 +150,21 @@ mod test {
145150
assert_eq!(result, "const t = 5;\n");
146151
}
147152
}
153+
154+
#[test]
155+
fn syntax_error_from_external_formatter() {
156+
let config = crate::configuration::ConfigurationBuilder::new().build();
157+
let result = format_text(FormatTextOptions {
158+
path: &std::path::PathBuf::from("test.ts"),
159+
extension: None,
160+
text: "const content = html`<div>broken html</p>`".into(),
161+
config: &config,
162+
external_formatter: Some(&|media_type, _text, _config| {
163+
assert!(matches!(media_type, deno_ast::MediaType::Html));
164+
Err(anyhow::anyhow!("Syntax error from external formatter"))
165+
}),
166+
});
167+
assert!(result.is_err());
168+
assert_eq!(result.unwrap_err().to_string(), "Error formatting tagged template literal at line 0: Syntax error from external formatter");
169+
}
148170
}

src/generation/context.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ use crate::utils::Stack;
4545
/// cases the templates will be left as they are.
4646
///
4747
/// Only templates with no interpolation are supported.
48-
pub type ExternalFormatter = dyn Fn(MediaType, String, &Configuration) -> Option<String>;
48+
pub type ExternalFormatter = dyn Fn(MediaType, String, &Configuration) -> anyhow::Result<Option<String>>;
49+
50+
pub(crate) struct GenerateDiagnostic {
51+
pub message: String,
52+
}
4953

5054
pub struct Context<'a> {
5155
pub media_type: MediaType,
@@ -70,6 +74,7 @@ pub struct Context<'a> {
7074
/// Used for ensuring nodes are parsed in order.
7175
#[cfg(debug_assertions)]
7276
pub last_generated_node_pos: SourcePos,
77+
pub diagnostics: Vec<GenerateDiagnostic>,
7378
}
7479

7580
impl<'a> Context<'a> {
@@ -102,6 +107,7 @@ impl<'a> Context<'a> {
102107
expr_stmt_single_line_parent_brace_ref: None,
103108
#[cfg(debug_assertions)]
104109
last_generated_node_pos: deno_ast::SourceTextInfoProvider::text_info(&program).range().start.into(),
110+
diagnostics: Vec::new(),
105111
}
106112
}
107113

src/generation/generate.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use super::*;
2525
use crate::configuration::*;
2626
use crate::utils;
2727

28-
pub fn generate(parsed_source: &ParsedSource, config: &Configuration, external_formatter: Option<&ExternalFormatter>) -> PrintItems {
28+
pub fn generate(parsed_source: &ParsedSource, config: &Configuration, external_formatter: Option<&ExternalFormatter>) -> anyhow::Result<PrintItems> {
2929
// eprintln!("Leading: {:?}", parsed_source.comments().leading_map());
3030
// eprintln!("Trailing: {:?}", parsed_source.comments().trailing_map());
3131

@@ -49,10 +49,14 @@ pub fn generate(parsed_source: &ParsedSource, config: &Configuration, external_f
4949
#[cfg(debug_assertions)]
5050
context.assert_end_of_file_state();
5151

52+
if let Some(diagnostic) = context.diagnostics.pop() {
53+
return Err(anyhow::anyhow!(diagnostic.message));
54+
}
55+
5256
if config.file_indent_level > 0 {
53-
with_indent_times(items, config.file_indent_level)
57+
Ok(with_indent_times(items, config.file_indent_level))
5458
} else {
55-
items
59+
Ok(items)
5660
}
5761
})
5862
}
@@ -3034,7 +3038,15 @@ fn maybe_gen_tagged_tpl_with_external_formatter<'a>(node: &TaggedTpl<'a>, contex
30343038
.unwrap();
30353039

30363040
// Then formats the text with the external formatter.
3037-
let formatted_tpl = external_formatter(media_type, text, context.config)?;
3041+
let formatted_tpl = match external_formatter(media_type, text, context.config) {
3042+
Ok(formatted_tpl) => formatted_tpl?,
3043+
Err(err) => {
3044+
context.diagnostics.push(context::GenerateDiagnostic {
3045+
message: format!("Error formatting tagged template literal at line {}: {}", node.start_line(), err),
3046+
});
3047+
return None;
3048+
}
3049+
};
30383050

30393051
let mut items = PrintItems::new();
30403052
items.push_sc(sc!("`"));

tests/spec_test.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use std::path::{Path, PathBuf};
22
use std::sync::Arc;
33

4+
use anyhow::Result;
45
use deno_ast::MediaType;
56
use dprint_core::configuration::*;
67
use dprint_development::*;
78
use dprint_plugin_typescript::configuration::*;
89
use dprint_plugin_typescript::*;
910

10-
fn external_formatter(media_type: MediaType, text: String, config: &Configuration) -> Option<String> {
11+
fn external_formatter(media_type: MediaType, text: String, config: &Configuration) -> Result<Option<String>> {
1112
match media_type {
1213
MediaType::Css => format_embedded_css(&text, config),
1314
MediaType::Html => format_html(&text, config),
@@ -16,7 +17,7 @@ fn external_formatter(media_type: MediaType, text: String, config: &Configuratio
1617
}
1718
}
1819

19-
fn format_embedded_css(text: &str, config: &Configuration) -> Option<String> {
20+
fn format_embedded_css(text: &str, config: &Configuration) -> Result<Option<String>> {
2021
use malva::config;
2122
let options = config::FormatOptions {
2223
layout: config::LayoutOptions {
@@ -27,9 +28,7 @@ fn format_embedded_css(text: &str, config: &Configuration) -> Option<String> {
2728
};
2829
// Wraps the text in a css block of `a { ... }`
2930
// to make it valid css (scss)
30-
let Ok(text) = malva::format_text(&format!("a{{\n{}\n}}", text), malva::Syntax::Scss, &options) else {
31-
return None;
32-
};
31+
let text = malva::format_text(&format!("a{{\n{}\n}}", text), malva::Syntax::Scss, &options)?;
3332
let mut buf = vec![];
3433
for (i, l) in text.lines().enumerate() {
3534
// skip the first line (a {)
@@ -47,10 +46,10 @@ fn format_embedded_css(text: &str, config: &Configuration) -> Option<String> {
4746
}
4847
buf.push(chars.as_str());
4948
}
50-
Some(buf.join("\n").to_string())
49+
Ok(Some(buf.join("\n").to_string()))
5150
}
5251

53-
fn format_html(text: &str, config: &Configuration) -> Option<String> {
52+
fn format_html(text: &str, config: &Configuration) -> Result<Option<String>> {
5453
use markup_fmt::config;
5554
let options = config::FormatOptions {
5655
layout: config::LayoutOptions {
@@ -59,19 +58,17 @@ fn format_html(text: &str, config: &Configuration) -> Option<String> {
5958
},
6059
..Default::default()
6160
};
62-
let Ok(text) = markup_fmt::format_text(text, markup_fmt::Language::Html, &options, |code, _| {
61+
let text = markup_fmt::format_text(text, markup_fmt::Language::Html, &options, |code, _| {
6362
Ok::<_, std::convert::Infallible>(code.into())
64-
}) else {
65-
return None;
66-
};
67-
Some(text.to_string())
63+
})?;
64+
Ok(Some(text.to_string()))
6865
}
6966

70-
fn format_sql(text: &str, config: &Configuration) -> Option<String> {
67+
fn format_sql(text: &str, config: &Configuration) -> Result<Option<String>> {
7168
let options = dprint_plugin_sql::configuration::ConfigurationBuilder::new()
7269
.indent_width(config.indent_width)
7370
.build();
74-
dprint_plugin_sql::format_text(Path::new("_path.sql"), text, &options).ok().flatten()
71+
dprint_plugin_sql::format_text(Path::new("_path.sql"), text, &options)
7572
}
7673

7774
fn main() {

0 commit comments

Comments
 (0)