-
-
Notifications
You must be signed in to change notification settings - Fork 739
feat(core): embedded formatting in html files #7467
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
Changes from all commits
2e10975
14dd77c
8299d80
361d009
201d671
4acdf3c
ac283b4
308300f
d588e92
f2e5187
7766957
4f237f9
d89a839
f7f38fe
f6218d3
ccb0459
caacb45
773581e
40f54fa
dfa3d03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| --- | ||
| source: crates/biome_cli/tests/snap_test.rs | ||
| expression: redactor(content) | ||
| --- | ||
| ## `biome.json` | ||
|
|
||
| ```json | ||
| { | ||
| "html": { | ||
| "formatter": { | ||
| "enabled": true, | ||
| "indentScriptAndStyle": true | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## `file.html` | ||
|
|
||
| ```html | ||
| <script> | ||
| function lorem() { | ||
| return "ipsum"; | ||
| } | ||
| </script> | ||
| <style> | ||
| #id .class div > p { | ||
| background-color: red; | ||
| align: center; | ||
| padding: 0; | ||
| } | ||
| </style> | ||
|
|
||
| ``` | ||
|
|
||
| # Emitted Messages | ||
|
|
||
| ```block | ||
| Formatted 1 file in <TIME>. Fixed 1 file. | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| --- | ||
| source: crates/biome_cli/tests/snap_test.rs | ||
| expression: redactor(content) | ||
| --- | ||
| ## `biome.json` | ||
|
|
||
| ```json | ||
| { | ||
| "html": { | ||
| "formatter": { | ||
| "enabled": true, | ||
| "indentScriptAndStyle": true | ||
| } | ||
| }, | ||
| "javascript": { | ||
| "formatter": { | ||
| "quoteStyle": "single" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## `file.html` | ||
|
|
||
| ```html | ||
| <script> | ||
| function lorem() { | ||
| return 'ipsum'; | ||
| } | ||
| </script> | ||
| <style> | ||
| #id .class div > p { | ||
| background-color: red; | ||
| align: center; | ||
| padding: 0; | ||
| } | ||
| </style> | ||
|
|
||
| ``` | ||
|
|
||
| # Emitted Messages | ||
|
|
||
| ```block | ||
| Formatted 1 file in <TIME>. Fixed 1 file. | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| #![expect(clippy::mutable_key_type)] | ||
|
|
||
| use super::tag::Tag; | ||
| use crate::format_element::tag::DedentMode; | ||
| use crate::prelude::tag::GroupMode; | ||
|
|
@@ -19,6 +20,10 @@ pub struct Document { | |
| } | ||
|
|
||
| impl Document { | ||
| pub fn new(elements: Vec<FormatElement>) -> Self { | ||
| Self { elements } | ||
| } | ||
|
|
||
| /// Sets [`expand`](tag::Group::expand) to [`GroupMode::Propagated`] if the group contains any of: | ||
| /// * a group with [`expand`](tag::Group::expand) set to [GroupMode::Propagated] or [GroupMode::Expand]. | ||
| /// * a non-soft [line break](FormatElement::Line) with mode [LineMode::Hard], [LineMode::Empty], or [LineMode::Literal]. | ||
|
|
@@ -132,6 +137,75 @@ impl Document { | |
| let mut interned = FxHashMap::default(); | ||
| propagate_expands(self, &mut enclosing, &mut interned); | ||
| } | ||
|
|
||
| pub fn into_elements(self) -> Vec<FormatElement> { | ||
| self.elements | ||
| } | ||
|
|
||
| pub fn as_elements(&self) -> &[FormatElement] { | ||
| &self.elements | ||
| } | ||
| } | ||
|
|
||
| pub trait DocumentVisitor { | ||
| /// Visit an element and optionally return a replacement | ||
| fn visit_element(&mut self, element: &FormatElement) -> Option<FormatElement>; | ||
| } | ||
|
|
||
| /// Applies a visitor to transform elements in the document | ||
| pub struct ElementTransformer; | ||
|
|
||
| impl ElementTransformer { | ||
| /// Visits a mutable [Document] and replaces its internal elements. | ||
| pub fn transform_document<V: DocumentVisitor>(document: &mut Document, visitor: &mut V) { | ||
| let elements = std::mem::take(&mut document.elements); | ||
| document.elements = Self::transform_elements(elements, visitor); | ||
| } | ||
|
|
||
| /// Iterates over each element of the document and map each element to a new element. The new element is | ||
| /// optionally crated using [DocumentVisitor::visit_element]. If no element is returned, the original element is kept. | ||
| /// | ||
| /// Nested data structures such as [FormatElement::Interned] and [FormatElement::BestFitting] use recursion and call | ||
| /// [Self::transform_elements] again. | ||
| fn transform_elements<V: DocumentVisitor>( | ||
| elements: Vec<FormatElement>, | ||
| visitor: &mut V, | ||
| ) -> Vec<FormatElement> { | ||
| elements | ||
| .into_iter() | ||
| .map(|element| { | ||
| // Transform nested elements first | ||
| let transformed_element = match element { | ||
| FormatElement::Interned(interned) => { | ||
| let nested_elements = interned.deref().to_vec(); | ||
| let transformed_nested = Self::transform_elements(nested_elements, visitor); | ||
| FormatElement::Interned(Interned::new(transformed_nested)) | ||
| } | ||
| FormatElement::BestFitting(best_fitting) => { | ||
| let variants: Vec<Box<[FormatElement]>> = best_fitting | ||
| .variants() | ||
| .iter() | ||
| .map(|variant| { | ||
| Self::transform_elements(variant.to_vec(), visitor) | ||
| .into_boxed_slice() | ||
| }) | ||
| .collect(); | ||
| // SAFETY: Safe because the number of variants is the same after the transformation | ||
| unsafe { | ||
ematipico marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| FormatElement::BestFitting(BestFittingElement::from_vec_unchecked( | ||
| variants, | ||
| )) | ||
| } | ||
|
Comment on lines
+194
to
+198
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add a safety comment for This 🤖 Prompt for AI Agents |
||
| } | ||
| other => other, | ||
| }; | ||
| // Then apply a visitor to the element itself | ||
| visitor | ||
| .visit_element(&transformed_element) | ||
| .unwrap_or(transformed_element) | ||
| }) | ||
| .collect() | ||
| } | ||
| } | ||
|
|
||
| impl From<Vec<FormatElement>> for Document { | ||
|
|
@@ -530,6 +604,9 @@ impl Format<IrFormatContext> for &[FormatElement] { | |
| write!(f, [text("fill(")])?; | ||
| } | ||
|
|
||
| StartEmbedded(_) => { | ||
| write!(f, [text("embedded(")])?; | ||
| } | ||
| StartEntry => { | ||
| // handled after the match for all start tags | ||
| } | ||
|
|
@@ -544,7 +621,8 @@ impl Format<IrFormatContext> for &[FormatElement] { | |
| | EndGroup | ||
| | EndLineSuffix | ||
| | EndDedent(_) | ||
| | EndVerbatim => { | ||
| | EndVerbatim | ||
| | EndEmbedded => { | ||
| write!(f, [ContentArrayEnd, text(")")])?; | ||
| } | ||
| }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add an explanation of how this algorithm works, including the role of the
base_pathin it? As far as I can see theElementPathis not used for anything useful, but if it is, I'm not seeing it.