Skip to content

refactor(BREAKING): external formatter accepts string instead of MediaType #723

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 23, 2025
Merged
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
4 changes: 2 additions & 2 deletions src/format_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ mod test {
extension: None,
text: "const content = html`<div>broken html</p>`".into(),
config: &config,
external_formatter: Some(&|media_type, _text, _config| {
assert!(matches!(media_type, deno_ast::MediaType::Html));
external_formatter: Some(&|lang, _text, _config| {
assert!(matches!(lang, "html"));
Err(anyhow::anyhow!("Syntax error from external formatter"))
}),
});
Expand Down
9 changes: 5 additions & 4 deletions src/generation/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ use super::*;
use crate::configuration::*;
use crate::utils::Stack;

/// A callback that will be called when encountering certain tagged templates.
/// A callback that will be called when encountering tagged templates.
///
/// Currently supports `css`, `html` and `sql` tagged templated.
/// It is up to the caller to decide if a certain tagged template should be formatted
/// by the external formatter.
///
/// Examples:
/// ```ignore
Expand All @@ -41,11 +42,11 @@ use crate::utils::Stack;
/// active IS TRUE;
/// ```
///
/// External formatter should return `None` if it doesn't understand given `MediaType`, in such
/// External formatter should return `None` if it doesn't understand given language, in such
/// cases the templates will be left as they are.
///
/// Only templates with no interpolation are supported.
pub type ExternalFormatter = dyn Fn(MediaType, String, &Configuration) -> anyhow::Result<Option<String>>;
pub type ExternalFormatter = dyn Fn(&str, String, &Configuration) -> anyhow::Result<Option<String>>;

pub(crate) struct GenerateDiagnostic {
pub message: String,
Expand Down
25 changes: 9 additions & 16 deletions src/generation/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3016,13 +3016,13 @@ fn gen_spread_element<'a>(node: &SpreadElement<'a>, context: &mut Context<'a>) -
/// Detects the type of embedded language automatically.
fn maybe_gen_tagged_tpl_with_external_formatter<'a>(node: &TaggedTpl<'a>, context: &mut Context<'a>) -> Option<PrintItems> {
let external_formatter = context.external_formatter.as_ref()?;
let media_type = detect_embedded_language_type(node)?;
let embedded_lang = normalize_embedded_language_type(node)?;

let placeholder_css = "@dpr1nt_";
let placeholder_other = "dpr1nt_";
// First creates text with placeholders for the expressions.
let placeholder_text = match media_type {
MediaType::Css => placeholder_css,
let placeholder_text = match embedded_lang {
"css" => placeholder_css,
_ => placeholder_other,
};
let text = capacity_builder::StringBuilder::<String>::build(|builder| {
Expand All @@ -3043,7 +3043,7 @@ fn maybe_gen_tagged_tpl_with_external_formatter<'a>(node: &TaggedTpl<'a>, contex
.unwrap();

// Then formats the text with the external formatter.
let formatted_tpl = match external_formatter(media_type, text.replace(r"\\", "\\"), context.config) {
let formatted_tpl = match external_formatter(embedded_lang, text.replace(r"\\", "\\"), context.config) {
Ok(formatted_tpl) => formatted_tpl?.replace("\\", r"\\"),
Err(err) => {
context.diagnostics.push(context::GenerateDiagnostic {
Expand Down Expand Up @@ -3102,29 +3102,22 @@ fn maybe_gen_tagged_tpl_with_external_formatter<'a>(node: &TaggedTpl<'a>, contex
Some(items)
}

/// Detects the type of embedded language in a tagged template literal.
fn detect_embedded_language_type<'a>(node: &TaggedTpl<'a>) -> Option<MediaType> {
/// Normalizes the type of embedded language in a tagged template literal.
fn normalize_embedded_language_type<'a>(node: &TaggedTpl<'a>) -> Option<&'a str> {
match node.tag {
Expr::Ident(ident) => {
match ident.sym().as_str() {
"css" => Some(MediaType::Css), // css`...`
"html" => Some(MediaType::Html), // html`...`
"sql" => Some(MediaType::Sql), // sql`...`
_ => None,
}
}
Expr::Ident(ident) => return Some(ident.sym().as_str()),
Expr::Member(member_expr) => {
if let Expr::Ident(ident) = member_expr.obj {
if ident.sym().as_str() == "styled" {
return Some(MediaType::Css); // styled.foo`...`
return Some("css"); // styled.foo`...`
}
}
None
}
Expr::Call(call_expr) => {
if let Callee::Expr(Expr::Ident(ident)) = call_expr.callee {
if ident.sym().as_str() == "styled" {
return Some(MediaType::Css); // styled(Button)`...`
return Some("css"); // styled(Button)`...`
}
}
None
Expand Down
13 changes: 6 additions & 7 deletions tests/spec_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;

use anyhow::Result;
use deno_ast::MediaType;
use dprint_core::configuration::*;
use dprint_development::*;
use dprint_plugin_typescript::configuration::*;
use dprint_plugin_typescript::*;

fn external_formatter(media_type: MediaType, text: String, config: &Configuration) -> Result<Option<String>> {
match media_type {
MediaType::Css => format_embedded_css(&text, config),
MediaType::Html => format_html(&text, config),
MediaType::Sql => format_sql(&text, config),
_ => unreachable!(),
fn external_formatter(lang: &str, text: String, config: &Configuration) -> Result<Option<String>> {
match lang {
"css" => format_embedded_css(&text, config),
"html" => format_html(&text, config),
"sql" => format_sql(&text, config),
_ => Ok(None),
}
}

Expand Down
Loading