mod comments;
pub mod context;
mod css;
mod cst;
mod generated;
mod prelude;
mod separated;
mod utils;
use std::borrow::Cow;
use crate::comments::CssCommentStyle;
pub(crate) use crate::context::CssFormatContext;
use crate::context::CssFormatOptions;
use crate::cst::FormatCssSyntaxNode;
use biome_css_syntax::{
AnyCssDeclarationListBlock, AnyCssRule, AnyCssRuleListBlock, AnyCssValue, CssLanguage,
CssSyntaxKind, CssSyntaxNode, CssSyntaxToken,
};
use biome_formatter::comments::Comments;
use biome_formatter::prelude::*;
use biome_formatter::token::string::ToAsciiLowercaseCow;
use biome_formatter::trivia::format_skipped_token_trivia;
use biome_formatter::{
write, CstFormatContext, FormatContext, FormatLanguage, FormatOwnedWithRule, FormatRefWithRule,
TransformSourceMap,
};
use biome_formatter::{Formatted, Printed};
use biome_rowan::{AstNode, SyntaxNode, TextRange};
pub(crate) trait AsFormat<Context> {
type Format<'a>: biome_formatter::Format<Context>
where
Self: 'a;
fn format(&self) -> Self::Format<'_>;
}
impl<T, C> AsFormat<C> for &T
where
T: AsFormat<C>,
{
type Format<'a> = T::Format<'a> where Self: 'a;
fn format(&self) -> Self::Format<'_> {
AsFormat::format(&**self)
}
}
impl<T, C> AsFormat<C> for biome_rowan::SyntaxResult<T>
where
T: AsFormat<C>,
{
type Format<'a> = biome_rowan::SyntaxResult<T::Format<'a>> where Self: 'a;
fn format(&self) -> Self::Format<'_> {
match self {
Ok(value) => Ok(value.format()),
Err(err) => Err(*err),
}
}
}
impl<T, C> AsFormat<C> for Option<T>
where
T: AsFormat<C>,
{
type Format<'a> = Option<T::Format<'a>> where Self: 'a;
fn format(&self) -> Self::Format<'_> {
self.as_ref().map(|value| value.format())
}
}
pub(crate) trait IntoFormat<Context> {
type Format: biome_formatter::Format<Context>;
fn into_format(self) -> Self::Format;
}
impl<T, Context> IntoFormat<Context> for biome_rowan::SyntaxResult<T>
where
T: IntoFormat<Context>,
{
type Format = biome_rowan::SyntaxResult<T::Format>;
fn into_format(self) -> Self::Format {
self.map(IntoFormat::into_format)
}
}
impl<T, Context> IntoFormat<Context> for Option<T>
where
T: IntoFormat<Context>,
{
type Format = Option<T::Format>;
fn into_format(self) -> Self::Format {
self.map(IntoFormat::into_format)
}
}
pub(crate) trait FormattedIterExt {
fn formatted<Context>(self) -> FormattedIter<Self, Self::Item, Context>
where
Self: Iterator + Sized,
Self::Item: IntoFormat<Context>,
{
FormattedIter {
inner: self,
options: std::marker::PhantomData,
}
}
}
impl<I> FormattedIterExt for I where I: std::iter::Iterator {}
pub(crate) struct FormattedIter<Iter, Item, Context>
where
Iter: Iterator<Item = Item>,
{
inner: Iter,
options: std::marker::PhantomData<Context>,
}
impl<Iter, Item, Context> std::iter::Iterator for FormattedIter<Iter, Item, Context>
where
Iter: Iterator<Item = Item>,
Item: IntoFormat<Context>,
{
type Item = Item::Format;
fn next(&mut self) -> Option<Self::Item> {
Some(self.inner.next()?.into_format())
}
}
impl<Iter, Item, Context> std::iter::FusedIterator for FormattedIter<Iter, Item, Context>
where
Iter: std::iter::FusedIterator<Item = Item>,
Item: IntoFormat<Context>,
{
}
impl<Iter, Item, Context> std::iter::ExactSizeIterator for FormattedIter<Iter, Item, Context>
where
Iter: Iterator<Item = Item> + std::iter::ExactSizeIterator,
Item: IntoFormat<Context>,
{
}
pub(crate) type CssFormatter<'buf> = Formatter<'buf, CssFormatContext>;
pub(crate) trait FormatNodeRule<N>
where
N: AstNode<Language = CssLanguage>,
{
fn fmt(&self, node: &N, f: &mut CssFormatter) -> FormatResult<()> {
if self.is_suppressed(node, f) {
return write!(f, [format_suppressed_node(node.syntax())]);
}
self.fmt_leading_comments(node, f)?;
self.fmt_fields(node, f)?;
self.fmt_dangling_comments(node, f)?;
self.fmt_trailing_comments(node, f)
}
fn fmt_fields(&self, node: &N, f: &mut CssFormatter) -> FormatResult<()>;
fn is_suppressed(&self, node: &N, f: &CssFormatter) -> bool {
f.context().comments().is_suppressed(node.syntax())
}
fn fmt_leading_comments(&self, node: &N, f: &mut CssFormatter) -> FormatResult<()> {
format_leading_comments(node.syntax()).fmt(f)
}
fn fmt_dangling_comments(&self, node: &N, f: &mut CssFormatter) -> FormatResult<()> {
format_dangling_comments(node.syntax())
.with_soft_block_indent()
.fmt(f)
}
fn fmt_trailing_comments(&self, node: &N, f: &mut CssFormatter) -> FormatResult<()> {
format_trailing_comments(node.syntax()).fmt(f)
}
}
pub(crate) trait FormatBogusNodeRule<N>
where
N: AstNode<Language = CssLanguage>,
{
fn fmt(&self, node: &N, f: &mut CssFormatter) -> FormatResult<()> {
format_bogus_node(node.syntax()).fmt(f)
}
}
#[derive(Debug, Default, Clone)]
pub struct CssFormatLanguage {
options: CssFormatOptions,
}
impl CssFormatLanguage {
pub fn new(options: CssFormatOptions) -> Self {
Self { options }
}
}
impl FormatLanguage for CssFormatLanguage {
type SyntaxLanguage = CssLanguage;
type Context = CssFormatContext;
type FormatRule = FormatCssSyntaxNode;
fn is_range_formatting_node(&self, node: &SyntaxNode<Self::SyntaxLanguage>) -> bool {
AnyCssDeclarationListBlock::can_cast(node.kind())
|| AnyCssRuleListBlock::can_cast(node.kind())
|| AnyCssValue::can_cast(node.kind())
|| AnyCssRule::can_cast(node.kind())
|| matches!(
node.kind(),
CssSyntaxKind::CSS_DECLARATION
| CssSyntaxKind::CSS_COMPONENT_VALUE_LIST
| CssSyntaxKind::CSS_SELECTOR_LIST
)
}
fn options(&self) -> &<Self::Context as FormatContext>::Options {
&self.options
}
fn create_context(
self,
root: &CssSyntaxNode,
source_map: Option<TransformSourceMap>,
) -> Self::Context {
let comments = Comments::from_node(root, &CssCommentStyle, source_map.as_ref());
CssFormatContext::new(self.options, comments).with_source_map(source_map)
}
}
#[derive(Default, Debug, Clone, Copy)]
pub(crate) struct FormatCssSyntaxToken;
impl FormatRule<CssSyntaxToken> for FormatCssSyntaxToken {
type Context = CssFormatContext;
fn fmt(&self, token: &CssSyntaxToken, f: &mut Formatter<Self::Context>) -> FormatResult<()> {
f.state_mut().track_token(token);
write!(f, [format_skipped_token_trivia(token)])?;
if token.kind().is_contextual_keyword() {
let original = token.text_trimmed();
match original.to_ascii_lowercase_cow() {
Cow::Borrowed(_) => write!(f, [format_trimmed_token(token)]),
Cow::Owned(lowercase) => write!(
f,
[dynamic_text(&lowercase, token.text_trimmed_range().start())]
),
}
} else {
write!(f, [format_trimmed_token(token)])
}
}
}
impl AsFormat<CssFormatContext> for CssSyntaxToken {
type Format<'a> = FormatRefWithRule<'a, CssSyntaxToken, FormatCssSyntaxToken>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(self, FormatCssSyntaxToken)
}
}
impl IntoFormat<CssFormatContext> for CssSyntaxToken {
type Format = FormatOwnedWithRule<CssSyntaxToken, FormatCssSyntaxToken>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(self, FormatCssSyntaxToken)
}
}
pub fn format_range(
options: CssFormatOptions,
root: &CssSyntaxNode,
range: TextRange,
) -> FormatResult<Printed> {
biome_formatter::format_range(root, range, CssFormatLanguage::new(options))
}
pub fn format_node(
options: CssFormatOptions,
root: &CssSyntaxNode,
) -> FormatResult<Formatted<CssFormatContext>> {
biome_formatter::format_node(root, CssFormatLanguage::new(options))
}
pub fn format_sub_tree(options: CssFormatOptions, root: &CssSyntaxNode) -> FormatResult<Printed> {
biome_formatter::format_sub_tree(root, CssFormatLanguage::new(options))
}
pub const fn can_format_css_yet() -> bool {
cfg!(debug_assertions)
}
#[cfg(test)]
mod tests {
use crate::context::CssFormatOptions;
use crate::format_node;
use biome_css_parser::{parse_css, CssParserOptions};
#[test]
fn smoke_test() {
let src = r#"html {}"#;
let parse = parse_css(src, CssParserOptions::default());
let options = CssFormatOptions::default();
let formatted = format_node(options, &parse.syntax()).unwrap();
assert_eq!(formatted.print().unwrap().as_code(), "html {\n}\n");
}
}