Skip to content

Commit

Permalink
feat: set up html formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
dyc3 committed Sep 3, 2024
1 parent b16def6 commit 6a8ddba
Show file tree
Hide file tree
Showing 32 changed files with 1,488 additions and 0 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions crates/biome_html_formatter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
authors.workspace = true
categories.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
name = "biome_html_formatter"
repository.workspace = true
version = "0.0.0"

[dependencies]
biome_diagnostics_categories = { workspace = true }
biome_formatter = { workspace = true }
biome_html_factory = { workspace = true }
biome_html_syntax = { workspace = true }
biome_rowan = { workspace = true }
biome_suppression = { workspace = true }

[lints]
workspace = true
89 changes: 89 additions & 0 deletions crates/biome_html_formatter/src/comments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use biome_diagnostics_categories::category;
use biome_formatter::{
comments::{
is_alignable_comment, CommentKind, CommentPlacement, CommentStyle, Comments,
DecoratedComment, SourceComment,
},
prelude::*,
write, FormatRule,
};
use biome_html_syntax::HtmlLanguage;
use biome_rowan::{SyntaxTriviaPieceComments, TextLen};
use biome_suppression::parse_suppression_comment;

use crate::context::HtmlFormatContext;

pub type HtmlComments = Comments<HtmlLanguage>;

#[derive(Default)]
pub struct FormatHtmlLeadingComment;

impl FormatRule<SourceComment<HtmlLanguage>> for FormatHtmlLeadingComment {
type Context = HtmlFormatContext;

fn fmt(
&self,
comment: &SourceComment<HtmlLanguage>,
f: &mut Formatter<Self::Context>,
) -> FormatResult<()> {
if is_alignable_comment(comment.piece()) {
let mut source_offset = comment.piece().text_range().start();

let mut lines = comment.piece().text().lines();

// SAFETY: Safe, `is_alignable_comment` only returns `true` for multiline comments
let first_line = lines.next().unwrap();
write!(f, [dynamic_text(first_line.trim_end(), source_offset)])?;

source_offset += first_line.text_len();

// Indent the remaining lines by one space so that all `*` are aligned.
write!(
f,
[&format_once(|f| {
for line in lines {
write!(
f,
[
hard_line_break(),
text(" "),
dynamic_text(line.trim(), source_offset)
]
)?;

source_offset += line.text_len();
}

Ok(())
})]
)
} else {
write!(f, [comment.piece().as_piece()])
}
}
}

#[derive(Eq, PartialEq, Copy, Clone, Debug, Default)]
pub struct HtmlCommentStyle;

impl CommentStyle for HtmlCommentStyle {
type Language = HtmlLanguage;

fn is_suppression(text: &str) -> bool {
parse_suppression_comment(text)
.filter_map(Result::ok)
.flat_map(|suppression| suppression.categories)
.any(|(key, _)| key == category!("format"))
}

fn get_comment_kind(_comment: &SyntaxTriviaPieceComments<HtmlLanguage>) -> CommentKind {
CommentKind::Block
}

fn place_comment(
&self,
_comment: DecoratedComment<Self::Language>,
) -> CommentPlacement<Self::Language> {
todo!();
}
}
187 changes: 187 additions & 0 deletions crates/biome_html_formatter/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
use std::{fmt, rc::Rc};

use biome_formatter::{
printer::PrinterOptions, AttributePosition, CstFormatContext, FormatContext, FormatOptions,
IndentStyle, IndentWidth, LineEnding, LineWidth, TransformSourceMap,
};
use biome_html_syntax::HtmlLanguage;

use crate::comments::{FormatHtmlLeadingComment, HtmlCommentStyle, HtmlComments};

#[derive(Debug, Clone, Default)]
pub struct HtmlFormatOptions {
/// The indent style.
indent_style: IndentStyle,

/// The indent width.
indent_width: IndentWidth,

/// The type of line ending.
line_ending: LineEnding,

/// What's the max width of a line. Defaults to 80.
line_width: LineWidth,

/// Attribute position style. By default auto.
attribute_position: AttributePosition,
}

impl HtmlFormatOptions {
pub fn new() -> Self {
Self {
..Default::default()
}
}

pub fn with_indent_style(mut self, indent_style: IndentStyle) -> Self {
self.indent_style = indent_style;
self
}

pub fn with_indent_width(mut self, indent_width: IndentWidth) -> Self {
self.indent_width = indent_width;
self
}

pub fn with_line_ending(mut self, line_ending: LineEnding) -> Self {
self.line_ending = line_ending;
self
}

pub fn with_line_width(mut self, line_width: LineWidth) -> Self {
self.line_width = line_width;
self
}

pub fn with_attribute_position(mut self, attribute_position: AttributePosition) -> Self {
self.attribute_position = attribute_position;
self
}

pub fn indent_style(&self) -> IndentStyle {
self.indent_style
}

pub fn indent_width(&self) -> IndentWidth {
self.indent_width
}

pub fn line_ending(&self) -> LineEnding {
self.line_ending
}

pub fn line_width(&self) -> LineWidth {
self.line_width
}

pub fn attribute_position(&self) -> AttributePosition {
self.attribute_position
}

pub fn set_indent_style(&mut self, indent_style: IndentStyle) {
self.indent_style = indent_style;
}

pub fn set_indent_width(&mut self, indent_width: IndentWidth) {
self.indent_width = indent_width;
}

pub fn set_line_ending(&mut self, line_ending: LineEnding) {
self.line_ending = line_ending;
}

pub fn set_line_width(&mut self, line_width: LineWidth) {
self.line_width = line_width;
}

pub fn set_attribute_position(&mut self, attribute_position: AttributePosition) {
self.attribute_position = attribute_position;
}
}

impl fmt::Display for HtmlFormatOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Indent style: {}", self.indent_style)?;
writeln!(f, "Indent width: {}", self.indent_width.value())?;
writeln!(f, "Line ending: {}", self.line_ending)?;
writeln!(f, "Line width: {}", self.line_width.value())?;
writeln!(f, "Attribute Position: {}", self.attribute_position)
}
}

impl FormatOptions for HtmlFormatOptions {
fn indent_style(&self) -> IndentStyle {
self.indent_style
}

fn indent_width(&self) -> IndentWidth {
self.indent_width
}

fn line_ending(&self) -> LineEnding {
self.line_ending
}

fn line_width(&self) -> LineWidth {
self.line_width
}

fn attribute_position(&self) -> AttributePosition {
self.attribute_position
}

fn bracket_spacing(&self) -> biome_formatter::BracketSpacing {
todo!("Bracket spacing doesn't really make sense for HTML")
}

fn as_print_options(&self) -> biome_formatter::prelude::PrinterOptions {
PrinterOptions::from(self)
}
}

#[derive(Debug, Clone)]
pub struct HtmlFormatContext {
options: HtmlFormatOptions,

/// The comments of the nodes and tokens in the document.
comments: Rc<HtmlComments>,

source_map: Option<TransformSourceMap>,
}

impl HtmlFormatContext {
pub fn new(options: HtmlFormatOptions, comments: HtmlComments) -> Self {
Self {
options,
comments: Rc::new(comments),
source_map: None,
}
}

pub fn with_source_map(mut self, source_map: Option<TransformSourceMap>) -> Self {
self.source_map = source_map;
self
}
}

impl FormatContext for HtmlFormatContext {
type Options = HtmlFormatOptions;

fn options(&self) -> &Self::Options {
&self.options
}

fn source_map(&self) -> Option<&TransformSourceMap> {
self.source_map.as_ref()
}
}

impl CstFormatContext for HtmlFormatContext {
type Language = HtmlLanguage;
type Style = HtmlCommentStyle;
type CommentRule = FormatHtmlLeadingComment;

fn comments(&self) -> &HtmlComments {
&self.comments
}
}
31 changes: 31 additions & 0 deletions crates/biome_html_formatter/src/cst.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use biome_formatter::{Format, FormatOwnedWithRule, FormatRefWithRule, FormatResult};

use crate::{AsFormat, HtmlFormatContext, HtmlFormatter, IntoFormat};
use biome_html_syntax::{map_syntax_node, HtmlSyntaxNode};

#[derive(Debug, Copy, Clone, Default)]
pub struct FormatHtmlSyntaxNode;

impl biome_formatter::FormatRule<HtmlSyntaxNode> for FormatHtmlSyntaxNode {
type Context = HtmlFormatContext;

fn fmt(&self, node: &HtmlSyntaxNode, f: &mut HtmlFormatter) -> FormatResult<()> {
map_syntax_node!(node.clone(), node => node.format().fmt(f))
}
}

impl AsFormat<HtmlFormatContext> for HtmlSyntaxNode {
type Format<'a> = FormatRefWithRule<'a, HtmlSyntaxNode, FormatHtmlSyntaxNode>;

fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(self, FormatHtmlSyntaxNode)
}
}

impl IntoFormat<HtmlFormatContext> for HtmlSyntaxNode {
type Format = FormatOwnedWithRule<HtmlSyntaxNode, FormatHtmlSyntaxNode>;

fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(self, FormatHtmlSyntaxNode)
}
}
Loading

0 comments on commit 6a8ddba

Please sign in to comment.