Skip to content

Commit

Permalink
feat(css_formatter): support @page and margin at-rules (#1331)
Browse files Browse the repository at this point in the history
  • Loading branch information
faultyserver authored Dec 25, 2023
1 parent c9458d3 commit 7c940e4
Show file tree
Hide file tree
Showing 15 changed files with 838 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::prelude::*;
use biome_css_syntax::CssDeclarationOrAtRuleBlock;
use biome_rowan::AstNode;
use biome_css_syntax::{CssDeclarationOrAtRuleBlock, CssDeclarationOrAtRuleBlockFields};
use biome_formatter::write;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssDeclarationOrAtRuleBlock;
impl FormatNodeRule<CssDeclarationOrAtRuleBlock> for FormatCssDeclarationOrAtRuleBlock {
Expand All @@ -9,6 +10,32 @@ impl FormatNodeRule<CssDeclarationOrAtRuleBlock> for FormatCssDeclarationOrAtRul
node: &CssDeclarationOrAtRuleBlock,
f: &mut CssFormatter,
) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
let CssDeclarationOrAtRuleBlockFields {
l_curly_token,
items,
r_curly_token,
} = node.as_fields();

// When the list is empty, we still print a hard line to put the
// closing curly on the next line.
if items.is_empty() {
write!(
f,
[
l_curly_token.format(),
hard_line_break(),
r_curly_token.format()
]
)
} else {
write!(
f,
[
l_curly_token.format(),
block_indent(&items.format()),
r_curly_token.format()
]
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::prelude::*;
use biome_css_syntax::CssDeclarationWithSemicolon;
use biome_rowan::AstNode;
use biome_css_syntax::{CssDeclarationWithSemicolon, CssDeclarationWithSemicolonFields};
use biome_formatter::write;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssDeclarationWithSemicolon;
impl FormatNodeRule<CssDeclarationWithSemicolon> for FormatCssDeclarationWithSemicolon {
Expand All @@ -9,6 +10,11 @@ impl FormatNodeRule<CssDeclarationWithSemicolon> for FormatCssDeclarationWithSem
node: &CssDeclarationWithSemicolon,
f: &mut CssFormatter,
) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
let CssDeclarationWithSemicolonFields {
declaration,
semicolon_token,
} = node.as_fields();

write!(f, [declaration.format(), semicolon_token.format()])
}
}
33 changes: 30 additions & 3 deletions crates/biome_css_formatter/src/css/auxiliary/page_at_rule_block.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
use crate::prelude::*;
use biome_css_syntax::CssPageAtRuleBlock;
use biome_rowan::AstNode;
use biome_css_syntax::{CssPageAtRuleBlock, CssPageAtRuleBlockFields};
use biome_formatter::write;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssPageAtRuleBlock;
impl FormatNodeRule<CssPageAtRuleBlock> for FormatCssPageAtRuleBlock {
fn fmt_fields(&self, node: &CssPageAtRuleBlock, f: &mut CssFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
let CssPageAtRuleBlockFields {
l_curly_token,
items,
r_curly_token,
} = node.as_fields();

// When the list is empty, we still print a hard line to put the
// closing curly on the next line.
if items.is_empty() {
write!(
f,
[
l_curly_token.format(),
hard_line_break(),
r_curly_token.format()
]
)
} else {
write!(
f,
[
l_curly_token.format(),
block_indent(&items.format()),
r_curly_token.format()
]
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use crate::prelude::*;
use biome_css_syntax::CssPageSelectorPseudo;
use biome_rowan::AstNode;
use biome_css_syntax::{CssPageSelectorPseudo, CssPageSelectorPseudoFields};
use biome_formatter::write;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssPageSelectorPseudo;
impl FormatNodeRule<CssPageSelectorPseudo> for FormatCssPageSelectorPseudo {
fn fmt_fields(&self, node: &CssPageSelectorPseudo, f: &mut CssFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
let CssPageSelectorPseudoFields {
colon_token,
selector,
} = node.as_fields();

write!(f, [colon_token.format(), selector.format()])
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::prelude::*;
use biome_css_syntax::CssDeclarationList;
use biome_formatter::separated::TrailingSeparator;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssDeclarationList;
impl FormatRule<CssDeclarationList> for FormatCssDeclarationList {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
use crate::prelude::*;
use biome_css_syntax::CssDeclarationOrAtRuleList;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssDeclarationOrAtRuleList;
impl FormatRule<CssDeclarationOrAtRuleList> for FormatCssDeclarationOrAtRuleList {
type Context = CssFormatContext;
fn fmt(&self, node: &CssDeclarationOrAtRuleList, f: &mut CssFormatter) -> FormatResult<()> {
f.join().entries(node.iter().formatted()).finish()
// This is one of the few cases where we _do_ want to respect empty
// lines from the input, so we can use `join_nodes_with_hardline`.
let mut join = f.join_nodes_with_hardline();

for declaration_or_at_rule in node {
join.entry(
declaration_or_at_rule.syntax(),
&format_or_verbatim(declaration_or_at_rule.format()),
);
}

join.finish()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ pub(crate) struct FormatCssPageAtRuleItemList;
impl FormatRule<CssPageAtRuleItemList> for FormatCssPageAtRuleItemList {
type Context = CssFormatContext;
fn fmt(&self, node: &CssPageAtRuleItemList, f: &mut CssFormatter) -> FormatResult<()> {
f.join().entries(node.iter().formatted()).finish()
// This is one of the few cases where we _do_ want to respect empty
// lines from the input, so we can use `join_nodes_with_hardline`.
let mut joiner = f.join_nodes_with_hardline();

for item in node.iter() {
joiner.entry(item.syntax(), &item.format());
}

joiner.finish()
}
}
22 changes: 21 additions & 1 deletion crates/biome_css_formatter/src/css/lists/page_selector_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ pub(crate) struct FormatCssPageSelectorList;
impl FormatRule<CssPageSelectorList> for FormatCssPageSelectorList {
type Context = CssFormatContext;
fn fmt(&self, node: &CssPageSelectorList, f: &mut CssFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
// Using `join_with` instead of `join_nodes_with_soft_line` to avoid
// preserving empty lines from the input source. See the comment in
// [FormatCssSelectorList] for more information.
let separator = soft_line_break_or_space();
let mut joiner = f.join_with(&separator);

for formatted in node.format_separated(",") {
// Each selector gets `indent` added in case it breaks over multiple
// lines. The break is added here rather than in each selector both
// for simplicity and to avoid recursively adding indents when
// selectors are nested within other rules. The group is then added
// around the indent to ensure that it tries using a flat layout
// first and only expands when the single selector can't fit the line.
//
// For example, a selector like `div span a` is structured like
// `[div, [span, [a]]]`, so `a` would end up double-indented if it
// was handled by the selector rather than here.
joiner.entry(&group(&indent(&formatted)));
}

joiner.finish()
}
}
9 changes: 6 additions & 3 deletions crates/biome_css_formatter/src/css/selectors/page_selector.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use crate::prelude::*;
use biome_css_syntax::CssPageSelector;
use biome_rowan::AstNode;
use biome_css_syntax::{CssPageSelector, CssPageSelectorFields};
use biome_formatter::{format_args, write};

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssPageSelector;
impl FormatNodeRule<CssPageSelector> for FormatCssPageSelector {
fn fmt_fields(&self, node: &CssPageSelector, f: &mut CssFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
let CssPageSelectorFields { ty, pseudos } = node.as_fields();

write!(f, [group(&format_args![ty.format(), pseudos.format()])])
}
}
16 changes: 13 additions & 3 deletions crates/biome_css_formatter/src/css/statements/margin_at_rule.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
use crate::prelude::*;
use biome_css_syntax::CssMarginAtRule;
use biome_rowan::AstNode;
use biome_css_syntax::{CssMarginAtRule, CssMarginAtRuleFields};
use biome_formatter::write;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssMarginAtRule;
impl FormatNodeRule<CssMarginAtRule> for FormatCssMarginAtRule {
fn fmt_fields(&self, node: &CssMarginAtRule, f: &mut CssFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
let CssMarginAtRuleFields {
at_token,
name,
block,
} = node.as_fields();

write!(
f,
[at_token.format(), name.format(), space(), block.format()]
)
}
}
22 changes: 19 additions & 3 deletions crates/biome_css_formatter/src/css/statements/page_at_rule.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
use crate::prelude::*;
use biome_css_syntax::CssPageAtRule;
use biome_rowan::AstNode;
use biome_css_syntax::{CssPageAtRule, CssPageAtRuleFields};
use biome_formatter::write;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssPageAtRule;
impl FormatNodeRule<CssPageAtRule> for FormatCssPageAtRule {
fn fmt_fields(&self, node: &CssPageAtRule, f: &mut CssFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
let CssPageAtRuleFields {
page_token,
selectors,
block,
} = node.as_fields();

write!(
f,
[
page_token.format(),
space(),
group(&indent(&selectors.format())),
space(),
block.format()
]
)
}
}
74 changes: 74 additions & 0 deletions crates/biome_css_formatter/tests/specs/css/atrule/page.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
@page{ }
@page {


}
@page{margin: 1cm;}
@page
{
margin: 1cm;}
@page :first {margin: 2cm;}
@page
:first
{

margin: 2cm;

}

@page :left {}
@page LandscapeTable {}
@page CompanyLetterHead:first {} /* identifier and pseudo page. */
@page:first {}
@page toc, index {}
@page
toc,
index
{}

@page :blank:first { }

@page {

@top-left {}


@bottom-center {}
}

@page :left { @left-middle {}}

@page :right {

@right-middle
{

}}

@page :first
{
@bottom-left-corner {}
@bottom-right-corner {

}
}

@page :first {
color: green;

@top-left {content: "foo";


color: blue;
} @top-right { content: "bar"; }
}
@page :first {
color: green;

@top-left { content: "foo"; color: blue; } @top-right { content: "bar"; }

margin: 20px;
}

@page :FIRST {}
@page :LEFT {}
Loading

0 comments on commit 7c940e4

Please sign in to comment.