-
-
Notifications
You must be signed in to change notification settings - Fork 533
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
166cbab
commit 93343b5
Showing
16 changed files
with
468 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,25 @@ | ||
use crate::prelude::*; | ||
use biome_css_syntax::CssBorder; | ||
use biome_rowan::AstNode; | ||
use crate::{prelude::*, utils::properties::FormatPropertyValueFields}; | ||
use biome_css_syntax::{CssBorder, CssBorderFields}; | ||
use biome_formatter::{format_args, write}; | ||
|
||
#[derive(Debug, Clone, Default)] | ||
pub(crate) struct FormatCssBorder; | ||
impl FormatNodeRule<CssBorder> for FormatCssBorder { | ||
fn fmt_fields(&self, node: &CssBorder, f: &mut CssFormatter) -> FormatResult<()> { | ||
format_verbatim_node(node.syntax()).fmt(f) | ||
let CssBorderFields { | ||
line_width, | ||
line_style, | ||
color, | ||
} = node.as_fields(); | ||
|
||
write!( | ||
f, | ||
[FormatPropertyValueFields::new(&format_args![ | ||
line_width.format(), | ||
line_style.format(), | ||
color.format(), | ||
]) | ||
.with_slot_map(node.concrete_order_slot_map())] | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,13 @@ | ||
use crate::prelude::*; | ||
use biome_css_syntax::CssLineStyle; | ||
use biome_rowan::AstNode; | ||
use biome_css_syntax::{CssLineStyle, CssLineStyleFields}; | ||
use biome_formatter::write; | ||
|
||
#[derive(Debug, Clone, Default)] | ||
pub(crate) struct FormatCssLineStyle; | ||
impl FormatNodeRule<CssLineStyle> for FormatCssLineStyle { | ||
fn fmt_fields(&self, node: &CssLineStyle, f: &mut CssFormatter) -> FormatResult<()> { | ||
format_verbatim_node(node.syntax()).fmt(f) | ||
let CssLineStyleFields { keyword } = node.as_fields(); | ||
|
||
write!(f, [keyword.format()]) | ||
} | ||
} |
9 changes: 6 additions & 3 deletions
9
crates/biome_css_formatter/src/css/auxiliary/line_width_keyword.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,13 @@ | ||
use crate::prelude::*; | ||
use biome_css_syntax::CssLineWidthKeyword; | ||
use biome_rowan::AstNode; | ||
use biome_css_syntax::{CssLineWidthKeyword, CssLineWidthKeywordFields}; | ||
use biome_formatter::write; | ||
|
||
#[derive(Debug, Clone, Default)] | ||
pub(crate) struct FormatCssLineWidthKeyword; | ||
impl FormatNodeRule<CssLineWidthKeyword> for FormatCssLineWidthKeyword { | ||
fn fmt_fields(&self, node: &CssLineWidthKeyword, f: &mut CssFormatter) -> FormatResult<()> { | ||
format_verbatim_node(node.syntax()).fmt(f) | ||
let CssLineWidthKeywordFields { keyword } = node.as_fields(); | ||
|
||
write!(f, [keyword.format()]) | ||
} | ||
} |
14 changes: 11 additions & 3 deletions
14
crates/biome_css_formatter/src/css/properties/border_property.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,18 @@ | ||
use crate::prelude::*; | ||
use biome_css_syntax::CssBorderProperty; | ||
use biome_rowan::AstNode; | ||
use biome_css_syntax::{CssBorderProperty, CssBorderPropertyFields}; | ||
use biome_formatter::write; | ||
#[derive(Debug, Clone, Default)] | ||
pub(crate) struct FormatCssBorderProperty; | ||
impl FormatNodeRule<CssBorderProperty> for FormatCssBorderProperty { | ||
fn fmt_fields(&self, node: &CssBorderProperty, f: &mut CssFormatter) -> FormatResult<()> { | ||
format_verbatim_node(node.syntax()).fmt(f) | ||
let CssBorderPropertyFields { | ||
name, | ||
colon_token, | ||
value, | ||
} = node.as_fields(); | ||
write!( | ||
f, | ||
[name.format(), colon_token.format(), space(), value.format()] | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
pub(crate) mod component_value_list; | ||
pub(crate) mod properties; | ||
pub(crate) mod string_utils; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
use crate::prelude::*; | ||
use biome_formatter::{write, Arguments}; | ||
|
||
/// Format all of the fields of a `PropertyValue` node in an arbitrary order, | ||
/// given by `slot_map`. | ||
/// | ||
/// Because the CSS grammar allows rules to specify fields that can appear | ||
/// in any order, there isn't always a linear mapping between the _declared_ | ||
/// order (how they appear in the grammar) and the _concrete_ order (how they | ||
/// appear in the source text) of the fields. The parser supports this by | ||
/// building a `slot_map` to map the declared order to the concrete order. | ||
/// | ||
/// When formatting, by default we want to preserve the ordering of fields as | ||
/// they were written in the source, but just using the `AstNode` alone will | ||
/// naturally re-write the value in the _declared_ order. To preserve the | ||
/// _concrete_ order, we can invert the `slot_map` and sort it to re-determine | ||
/// the ordering of fields and then iterate that list to format each field | ||
/// individually. | ||
/// | ||
/// ## Fields | ||
/// | ||
/// The caller provides a list of _pre-formatted_ fields, using the | ||
/// [`biome_formatter::format_args!`] macro. This way, it can either pass | ||
/// through a field as-is with default formatting, or it can apply any other | ||
/// formatting it once for that field: | ||
/// | ||
/// ```rust,ignore | ||
/// let formatted = format!(CssFormatContext::default(), [ | ||
/// FormatPropertyValueFields::new(&format_args![ | ||
/// text("a"), | ||
/// text("b"), | ||
/// group(&block_indent(&format_args![text("c"), hard_line_break(), text("d")])) | ||
/// ]) | ||
/// .with_slot_map([1, 2, 0]) | ||
/// ])?; | ||
/// | ||
/// assert_eq!("b | ||
/// \tc | ||
/// \td | ||
/// a", formatted.print()?.as_code()); | ||
/// ``` | ||
/// | ||
/// ## Concrete Ordering | ||
/// | ||
/// By default, using this struct will format the fields of the node in order. | ||
/// This is sufficient for nodes that don't have any dynamically-ordered | ||
/// fields, but for dynamic nodes that want to preserve the order of fields as | ||
/// they were given in the input, or for any node that wants to change the | ||
/// ordering of the fields, the caller will need to provide a `slot_map` that | ||
/// this struct can use to re-order the fields. | ||
/// | ||
/// To preserve the field order as it was written in the original source, use | ||
/// [biome_rowan::AstNodeSlotMap::concrete_order_slot_map], which will ensure | ||
/// the ordering matches what was given. This should be the default for most | ||
/// if not all dynamic nodes. | ||
/// | ||
/// ```rust,ignore | ||
/// .with_slot_map(node.concrete_order_slot_map()) | ||
/// ``` | ||
/// | ||
/// Any other method of building a slot map is also valid, but should generally | ||
/// be avoided, as ensuring consistency across formats is difficult without a | ||
/// strong heuristic. | ||
/// | ||
/// ## Grouping Fields (Future) | ||
/// | ||
/// In some cases, a property may want to group certain fields together in | ||
/// order to apply special formatting. As an example, consider a grammar like: | ||
/// | ||
/// ```ebnf | ||
/// font = | ||
/// (style: CssFontStyle || | ||
/// variant: CssFontVariant || | ||
/// weight: CssFontWeight)? | ||
/// size: CssNumber ( '/' line_height: CssLineHeight)? | ||
/// ``` | ||
/// | ||
/// Here, the `style`, `variant`, and `weight` fields can appear conditionally | ||
/// and in any order, but if `line_height` is present, it (and the slash token) | ||
/// must appear immediately adjacent to the `size` field. While it would be | ||
/// valid to just have the fields fill and wrap over lines as needed, the | ||
/// formatter might want to preserve the adjacency and ensure that `size` and | ||
/// `line_height` always get written on the same line. | ||
/// | ||
/// To do this, the value formatter can write both fields in a single group, | ||
/// and then use an `empty_field_slot` value in the slots where the other | ||
/// fields have been taken from: | ||
/// | ||
/// ```rust,ignore | ||
/// FormatPropertyValueFields::new(&format_args![ | ||
/// style.format(), | ||
/// variant.format(), | ||
/// weight.format(), | ||
/// group(&format_args![ | ||
/// size.format(), slash_token.format(), line_height.format() | ||
/// ]), | ||
/// empty_field_slot(), | ||
/// empty_field_slot() | ||
/// ]) | ||
/// .with_slot_map(node.concrete_order_slot_map()) | ||
/// ``` | ||
/// | ||
/// The `empty_field_slot()` values will tell this struct to skip formatting | ||
/// for that field, with the assumption that another field includes its value. | ||
pub struct FormatPropertyValueFields<'fmt, const N: usize> { | ||
slot_map: Option<[u8; N]>, | ||
fields: &'fmt Arguments<'fmt, CssFormatContext>, | ||
} | ||
|
||
impl<'fmt, const N: usize> FormatPropertyValueFields<'fmt, N> { | ||
pub fn new(fields: &'fmt Arguments<'fmt, CssFormatContext>) -> Self { | ||
Self { | ||
slot_map: None, | ||
fields, | ||
} | ||
} | ||
|
||
pub fn with_slot_map(mut self, slot_map: [u8; N]) -> Self { | ||
debug_assert!( | ||
self.fields.items().len() == N, | ||
"slot_map must specify the same number of fields as this struct contains" | ||
); | ||
self.slot_map = Some(slot_map); | ||
self | ||
} | ||
} | ||
|
||
impl<'fmt, const N: usize> Format<CssFormatContext> for FormatPropertyValueFields<'fmt, N> { | ||
fn fmt(&self, f: &mut CssFormatter) -> FormatResult<()> { | ||
let values = format_with(|f: &mut Formatter<'_, CssFormatContext>| { | ||
let mut filler = f.fill(); | ||
|
||
// First, determine the ordering of fields to use. If no slot_map is | ||
// provided along with the fields, then they can just be used in the | ||
// same order, but if a `slot_map` is present, then the fields are | ||
// re-ordered to match the concrete ordering from the source syntax. | ||
// | ||
// The fields are wrapped with `Option` for two reasons: for nodes | ||
// with slot maps, it simplifies how the re-ordered slice is built, and | ||
// it also allows empty/missing fields to be removed in the next step. | ||
match self.slot_map { | ||
None => { | ||
for field in self.fields.items() { | ||
filler.entry(&soft_line_break_or_space(), field); | ||
} | ||
} | ||
Some(slot_map) => { | ||
for slot in slot_map { | ||
// This condition ensures that missing values are _not_ included in the | ||
// fill. The generated `slot_map` for an AstNode guarantees that all | ||
// present fields have a tangible value here, while all absent fields | ||
// have this sentinel value ([biome_css_syntax::SLOT_MAP_EMPTY_VALUE]). | ||
// | ||
// This check is important to ensure that we don't add empty values to | ||
// the fill, since that would add double separators when we don't want | ||
// them. | ||
if slot == u8::MAX { | ||
continue; | ||
} | ||
|
||
let field = &self.fields.items()[slot as usize]; | ||
filler.entry(&soft_line_break_or_space(), field); | ||
} | ||
} | ||
}; | ||
|
||
filler.finish() | ||
}); | ||
|
||
write!(f, [group(&indent(&values))]) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
crates/biome_css_formatter/tests/specs/css/properties/border.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
div { | ||
/* Generic property tests */ | ||
border: InItial; | ||
border | ||
: | ||
inherit | ||
; | ||
|
||
border : zzz-unknown-value ; | ||
border : a, | ||
value list ; | ||
|
||
|
||
/* <line-style> */ | ||
border : SOLID; | ||
border: none | ||
; | ||
|
||
/* <line-width> */ | ||
border : ThIn; | ||
border: | ||
medium | ||
; | ||
border: 100px; | ||
|
||
/* <color> */ | ||
border: | ||
#fff; | ||
|
||
/* combinations */ | ||
border: 2px | ||
dotted; | ||
border : outset #f33; | ||
border:#000 medium | ||
|
||
dashed | ||
|
||
; | ||
} |
Oops, something went wrong.