Skip to content
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: 3 additions & 1 deletion crates/biome_css_formatter/src/css/auxiliary/metavariable.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::prelude::*;
use crate::verbatim::format_css_verbatim_node;
use biome_css_syntax::CssMetavariable;
use biome_rowan::AstNode;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssMetavariable;
impl FormatNodeRule<CssMetavariable> for FormatCssMetavariable {
fn fmt_fields(&self, node: &CssMetavariable, f: &mut CssFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
format_css_verbatim_node(node.syntax()).fmt(f)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::prelude::*;
use biome_css_syntax::CssValueAtRuleDeclarationClause;
use biome_rowan::AstNode;
use biome_css_syntax::{CssValueAtRuleDeclarationClause, CssValueAtRuleDeclarationClauseFields};
use biome_formatter::write;

#[derive(Debug, Clone, Default)]
pub(crate) struct FormatCssValueAtRuleDeclarationClause;
impl FormatNodeRule<CssValueAtRuleDeclarationClause> for FormatCssValueAtRuleDeclarationClause {
Expand All @@ -9,6 +10,8 @@ impl FormatNodeRule<CssValueAtRuleDeclarationClause> for FormatCssValueAtRuleDec
node: &CssValueAtRuleDeclarationClause,
f: &mut CssFormatter,
) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
let CssValueAtRuleDeclarationClauseFields { properties } = node.as_fields();

write!(f, [properties.format()])
}
}
2 changes: 2 additions & 0 deletions crates/biome_css_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ mod generated;
mod prelude;
mod separated;
mod utils;
mod verbatim;

use std::borrow::Cow;

use crate::comments::CssCommentStyle;
pub(crate) use crate::context::CssFormatContext;
use crate::context::CssFormatOptions;
use crate::cst::FormatCssSyntaxNode;
use crate::prelude::{format_bogus_node, format_suppressed_node};
use biome_css_syntax::{
AnyCssDeclarationBlock, AnyCssRule, AnyCssRuleBlock, AnyCssValue, CssLanguage, CssSyntaxKind,
CssSyntaxNode, CssSyntaxToken,
Expand Down
1 change: 1 addition & 0 deletions crates/biome_css_formatter/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
pub(crate) use crate::separated::FormatAstSeparatedListExtension;
pub(crate) use crate::{
AsFormat, CssFormatContext, CssFormatter, FormatNodeRule, FormattedIterExt as _, IntoFormat,
verbatim::*,
};
pub(crate) use biome_formatter::prelude::*;
pub(crate) use biome_rowan::{
Expand Down
203 changes: 203 additions & 0 deletions crates/biome_css_formatter/src/verbatim.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
use crate::context::CssFormatContext;
use biome_css_syntax::{CssLanguage, CssSyntaxNode};
use biome_formatter::format_element::tag::VerbatimKind;
use biome_formatter::formatter::Formatter;
use biome_formatter::prelude::{Tag, dynamic_text};
use biome_formatter::trivia::{FormatLeadingComments, FormatTrailingComments};
use biome_formatter::{
Buffer, CstFormatContext, Format, FormatContext, FormatElement, FormatError, FormatResult,
FormatWithRule, LINE_TERMINATORS, normalize_newlines,
};
use biome_rowan::{AstNode, Direction, SyntaxElement, TextRange};

/// "Formats" a node according to its original formatting in the source text. Being able to format
/// a node "as is" is useful if a node contains syntax errors. Formatting a node with syntax errors
/// has the risk that Biome misinterprets the structure of the code and formatting it could
/// "mess up" the developers, yet incomplete, work or accidentally introduce new syntax errors.
///
/// You may be inclined to call `node.text` directly. However, using `text` doesn't track the nodes
/// nor its children source mapping information, resulting in incorrect source maps for this subtree.
///
/// These nodes and tokens get tracked as [VerbatimKind::Verbatim], useful to understand
/// if these nodes still need to have their own implementation.
pub fn format_css_verbatim_node(node: &CssSyntaxNode) -> FormatCssVerbatimNode {
FormatCssVerbatimNode {
node,
kind: VerbatimKind::Verbatim {
length: node.text_range_with_trivia().len(),
},
format_comments: true,
}
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct FormatCssVerbatimNode<'node> {
node: &'node CssSyntaxNode,
kind: VerbatimKind,
format_comments: bool,
}

impl Format<CssFormatContext> for FormatCssVerbatimNode<'_> {
fn fmt(&self, f: &mut Formatter<CssFormatContext>) -> FormatResult<()> {
for element in self.node.descendants_with_tokens(Direction::Next) {
match element {
SyntaxElement::Token(token) => f.state_mut().track_token(&token),
SyntaxElement::Node(node) => {
let comments = f.context().comments();
comments.mark_suppression_checked(&node);

for comment in comments.leading_dangling_trailing_comments(&node) {
comment.mark_formatted();
}
}
}
}

// The trimmed range of a node is its range without any of its leading or trailing trivia.
// Except for nodes that used to be parenthesized, the range than covers the source from the
// `(` to the `)` (the trimmed range of the parenthesized expression, not the inner expression)
let trimmed_source_range = f.context().source_map().map_or_else(
|| self.node.text_trimmed_range(),
|source_map| source_map.trimmed_source_range(self.node),
);

f.write_element(FormatElement::Tag(Tag::StartVerbatim(self.kind)))?;

fn source_range<Context>(f: &Formatter<Context>, range: TextRange) -> TextRange
where
Context: CstFormatContext,
{
f.context()
.source_map()
.map_or_else(|| range, |source_map| source_map.source_range(range))
}

// Format all leading comments that are outside of the node's source range.
if self.format_comments {
let comments = f.context().comments().clone();
let leading_comments = comments.leading_comments(self.node);

let outside_trimmed_range = leading_comments.partition_point(|comment| {
comment.piece().text_range().end() <= trimmed_source_range.start()
});

let (outside_trimmed_range, in_trimmed_range) =
leading_comments.split_at(outside_trimmed_range);

biome_formatter::write!(f, [FormatLeadingComments::Comments(outside_trimmed_range)])?;

for comment in in_trimmed_range {
comment.mark_formatted();
}
}

// Find the first skipped token trivia, if any, and include it in the verbatim range because
// the comments only format **up to** but not including skipped token trivia.
let start_source = self
.node
.first_leading_trivia()
.into_iter()
.flat_map(|trivia| trivia.pieces())
.filter(|trivia| trivia.is_skipped())
.map(|trivia| source_range(f, trivia.text_range()).start())
.take_while(|start| *start < trimmed_source_range.start())
.next()
.unwrap_or_else(|| trimmed_source_range.start());

let original_source = f.context().source_map().map_or_else(
|| self.node.text_trimmed().to_string(),
|source_map| {
source_map
.source()
.text_slice(trimmed_source_range.cover_offset(start_source))
.to_string()
},
);

dynamic_text(
&normalize_newlines(&original_source, LINE_TERMINATORS),
self.node.text_trimmed_range().start(),
)
.fmt(f)?;

for comment in f.context().comments().dangling_comments(self.node) {
comment.mark_formatted();
}

// Format all trailing comments that are outside of the trimmed range.
if self.format_comments {
let comments = f.context().comments().clone();

let trailing_comments = comments.trailing_comments(self.node);

let outside_trimmed_range_start = trailing_comments.partition_point(|comment| {
source_range(f, comment.piece().text_range()).end() <= trimmed_source_range.end()
});

let (in_trimmed_range, outside_trimmed_range) =
trailing_comments.split_at(outside_trimmed_range_start);

for comment in in_trimmed_range {
comment.mark_formatted();
}

biome_formatter::write!(f, [FormatTrailingComments::Comments(outside_trimmed_range)])?;
}

f.write_element(FormatElement::Tag(Tag::EndVerbatim))
}
}

/// Formats bogus nodes. The difference between this method and `format_verbatim` is that this method
/// doesn't track nodes/tokens as [VerbatimKind::Verbatim]. They are just printed as they are.
pub fn format_bogus_node(node: &CssSyntaxNode) -> FormatCssVerbatimNode {
FormatCssVerbatimNode {
node,
kind: VerbatimKind::Bogus,
format_comments: true,
}
}

/// Format a node having formatter suppression comment applied to it
pub fn format_suppressed_node(node: &CssSyntaxNode) -> FormatCssVerbatimNode {
FormatCssVerbatimNode {
node,
kind: VerbatimKind::Suppressed,
format_comments: true,
}
}

/// Formats an object using its [`Format`] implementation but falls back to printing the object as
/// it is in the source document if formatting it returns an [`FormatError::SyntaxError`].
pub const fn format_or_verbatim<F>(inner: F) -> FormatNodeOrVerbatim<F> {
FormatNodeOrVerbatim { inner }
}

/// Formats a node or falls back to verbatim printing if formating this node fails.
#[derive(Copy, Clone)]
pub struct FormatNodeOrVerbatim<F> {
inner: F,
}

impl<F, Item> Format<CssFormatContext> for FormatNodeOrVerbatim<F>
where
F: FormatWithRule<CssFormatContext, Item = Item>,
Item: AstNode<Language = CssLanguage>,
{
fn fmt(&self, f: &mut Formatter<CssFormatContext>) -> FormatResult<()> {
let snapshot = Formatter::state_snapshot(f);

match self.inner.fmt(f) {
Ok(result) => Ok(result),

Err(FormatError::SyntaxError) => {
f.restore_state_snapshot(snapshot);

// Lists that yield errors are formatted as they were suppressed nodes.
// Doing so, the formatter formats the nodes/tokens as is.
format_suppressed_node(self.inner.item().syntax()).fmt(f)
}
Err(err) => Err(err),
}
}
}
37 changes: 8 additions & 29 deletions crates/biome_css_formatter/tests/specs/css/atrule/value.css.snap
Original file line number Diff line number Diff line change
Expand Up @@ -64,38 +64,17 @@ Quote style: Double Quotes
-----

```css
@value colors:
"./colors.css";
@value colors: "./colors.css";
@value primary, secondary from colors;
@value small as bp-small, medium, large as bp-large from "./breakpoints.css";
@value selectorValue:
secondary-color;
@value small:
(max-width:
@value selectorValue: secondary-color;
@value small: (max-width:
599px);
@value medium:
(min-width: 600px) and (max-width: 959px);
@value large:
(min-width: 960px);
@value primary:
#BF4040;
@value secondary:
#1F4F7F;
@value common-gradient:
transparent 75%,
@value medium: (min-width: 600px) and (max-width: 959px);
@value large: (min-width: 960px);
@value primary: #BF4040;
@value secondary: #1F4F7F;
@value common-gradient: transparent 75%,
var(--ring-line-color) 75%,
currentColor 79%;
```



## Unimplemented nodes/tokens

" colors:\n\"./colors.css\"" => 6..29
" selectorValue:\nsecondary-color" => 154..185
" small:\n(max-width:\n599px)" => 193..219
" medium:\n(min-width: 600px) and (max-width: 959px)" => 227..277
" large:\n(min-width: 960px)" => 285..311
" primary:\n#BF4040" => 319..336
" secondary:\n#1F4F7F" => 344..363
" common-gradient:\ntransparent 75%,\nvar(--ring-line-color) 75%,\ncurrentColor 79%" => 371..450
1 change: 0 additions & 1 deletion crates/biome_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ pub mod separated;
mod source_map;
pub mod token;
pub mod trivia;
mod verbatim;

use crate::formatter::Formatter;
use crate::group_id::UniqueGroupIdBuilder;
Expand Down
4 changes: 0 additions & 4 deletions crates/biome_formatter/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ pub use crate::trivia::{
pub use crate::diagnostics::FormatError;
pub use crate::format_element::document::Document;
pub use crate::format_element::tag::{LabelId, Tag, TagKind};
pub use crate::verbatim::{
format_bogus_node, format_or_verbatim, format_suppressed_node, format_verbatim_node,
format_verbatim_skipped,
};

pub use crate::{
Buffer as _, BufferExtensions, Format, Format as _, FormatResult, FormatRule,
Expand Down
2 changes: 2 additions & 0 deletions crates/biome_graphql_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ mod generated;
mod graphql;
mod prelude;
mod utils;
mod verbatim;

use crate::comments::GraphqlCommentStyle;
pub(crate) use crate::context::GraphqlFormatContext;
use crate::context::GraphqlFormatOptions;
use crate::cst::FormatGraphqlSyntaxNode;
use crate::prelude::{format_bogus_node, format_suppressed_node};
use biome_formatter::comments::Comments;
use biome_formatter::prelude::*;
use biome_formatter::{
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_graphql_formatter/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

pub(crate) use crate::{
AsFormat, FormatNodeRule, FormattedIterExt as _, GraphqlFormatContext, GraphqlFormatter,
IntoFormat,
IntoFormat, verbatim::*,
};
pub(crate) use biome_formatter::prelude::*;
pub(crate) use biome_rowan::{
Expand Down
Loading
Loading