-
-
Notifications
You must be signed in to change notification settings - Fork 963
feat(markdown): implement basic formatter features #9693
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
13752df
Implement basic formatting for Markdown
tidefield 9f12a10
Fix FormatMdTextualOptions usage
tidefield f26be38
Rebase with latest main
tidefield 182b255
Revert already formatted nodes
tidefield c125056
Remove complex formatting to save it to next PR
tidefield 6bff558
[autofix.ci] apply automated fixes
autofix-ci[bot] ff5ed7e
Update bullet.rs
tidefield 1a4bd5e
Address code review
tidefield b2ff64d
Address code review
tidefield 9a59616
Address clippy warning
tidefield 1dfa6dd
Address code review
tidefield a9d4b02
Implement typed reprensentation for fence tokens of MdInlineEmphasis
tidefield 0e5da05
clippy fix
tidefield File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
48 changes: 45 additions & 3 deletions
48
crates/biome_markdown_formatter/src/markdown/auxiliary/bullet.rs
This file contains hidden or 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,52 @@ | ||
| use crate::markdown::auxiliary::list_marker_prefix::FormatMdListMarkerPrefixOptions; | ||
| use crate::prelude::*; | ||
| use biome_markdown_syntax::MdBullet; | ||
| use biome_rowan::AstNode; | ||
| use biome_formatter::write; | ||
| use biome_markdown_syntax::{ | ||
| AnyMdBlock, AnyMdLeafBlock, MarkdownSyntaxKind, MdBullet, MdBulletFields, | ||
| }; | ||
| #[derive(Debug, Clone, Default)] | ||
| pub(crate) struct FormatMdBullet; | ||
| impl FormatNodeRule<MdBullet> for FormatMdBullet { | ||
| fn fmt_fields(&self, node: &MdBullet, f: &mut MarkdownFormatter) -> FormatResult<()> { | ||
| format_verbatim_node(node.syntax()).fmt(f) | ||
| let MdBulletFields { prefix, content } = node.as_fields(); | ||
|
|
||
| let prefix = prefix?; | ||
| let marker = prefix.marker()?; | ||
|
|
||
| // `* - - -` is a bullet containing a `-` thematic break. Normalizing `*` | ||
| // to `-` produces `- - - -` which CommonMark 4.1 parses as a thematic | ||
| // break, not a list item. Same for `+ - - -`. Skip normalization for marker | ||
| // but still format content through child formatters. | ||
| let target_marker = if marker.kind() == MarkdownSyntaxKind::MINUS | ||
| || first_block_is_dash_thematic_break(&content) | ||
| { | ||
| None | ||
| } else { | ||
| Some("-") | ||
| }; | ||
|
|
||
| write!( | ||
| f, | ||
| [prefix | ||
| .format() | ||
| .with_options(FormatMdListMarkerPrefixOptions { target_marker })] | ||
| )?; | ||
| content.format().fmt(f)?; | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| /// Returns true if the first block in `content` is a thematic break using `-`. | ||
| fn first_block_is_dash_thematic_break(content: &biome_markdown_syntax::MdBlockList) -> bool { | ||
| let Some(AnyMdBlock::AnyMdLeafBlock(AnyMdLeafBlock::MdThematicBreakBlock(block))) = | ||
| content.iter().next() | ||
| else { | ||
| return false; | ||
| }; | ||
| block | ||
| .parts() | ||
| .into_iter() | ||
| .find_map(|p| p.as_md_thematic_break_char().cloned()) | ||
| .and_then(|c| c.value().ok()) | ||
| .is_some_and(|t| t.text_trimmed() == "-") | ||
| } | ||
7 changes: 4 additions & 3 deletions
7
crates/biome_markdown_formatter/src/markdown/auxiliary/bullet_list_item.rs
This file contains hidden or 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,11 @@ | ||
| use crate::prelude::*; | ||
| use biome_markdown_syntax::MdBulletListItem; | ||
| use biome_rowan::AstNode; | ||
| use biome_formatter::write; | ||
| use biome_markdown_syntax::{MdBulletListItem, MdBulletListItemFields}; | ||
| #[derive(Debug, Clone, Default)] | ||
| pub(crate) struct FormatMdBulletListItem; | ||
| impl FormatNodeRule<MdBulletListItem> for FormatMdBulletListItem { | ||
| fn fmt_fields(&self, node: &MdBulletListItem, f: &mut MarkdownFormatter) -> FormatResult<()> { | ||
| format_verbatim_node(node.syntax()).fmt(f) | ||
| let MdBulletListItemFields { md_bullet_list } = node.as_fields(); | ||
| write!(f, [md_bullet_list.format()]) | ||
| } | ||
| } |
This file contains hidden or 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
25 changes: 22 additions & 3 deletions
25
crates/biome_markdown_formatter/src/markdown/auxiliary/inline_emphasis.rs
This file contains hidden or 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,29 @@ | ||
| use crate::prelude::*; | ||
| use biome_markdown_syntax::MdInlineEmphasis; | ||
| use biome_rowan::AstNode; | ||
| use biome_formatter::write; | ||
| use biome_markdown_syntax::{ | ||
| MdInlineEmphasis, MdInlineEmphasisFields, emphasis_ext::MdEmphasisFence, | ||
| }; | ||
| #[derive(Debug, Clone, Default)] | ||
| pub(crate) struct FormatMdInlineEmphasis; | ||
| impl FormatNodeRule<MdInlineEmphasis> for FormatMdInlineEmphasis { | ||
| fn fmt_fields(&self, node: &MdInlineEmphasis, f: &mut MarkdownFormatter) -> FormatResult<()> { | ||
| format_verbatim_node(node.syntax()).fmt(f) | ||
| let MdInlineEmphasisFields { | ||
| l_fence, | ||
| content, | ||
| r_fence, | ||
| } = node.as_fields(); | ||
|
|
||
| if node.fence().ok() == Some(MdEmphasisFence::DoubleStar) { | ||
| write!(f, [l_fence.format(), content.format(), r_fence.format()]) | ||
| } else { | ||
| write!( | ||
| f, | ||
| [ | ||
| format_replaced(&l_fence?, &token(MdEmphasisFence::DoubleStar.as_str())), | ||
| content.format(), | ||
| format_replaced(&r_fence?, &token(MdEmphasisFence::DoubleStar.as_str())), | ||
| ] | ||
| ) | ||
| } | ||
| } | ||
| } |
66 changes: 64 additions & 2 deletions
66
crates/biome_markdown_formatter/src/markdown/auxiliary/inline_italic.rs
This file contains hidden or 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,72 @@ | ||
| use crate::prelude::*; | ||
| use biome_markdown_syntax::MdInlineItalic; | ||
| use biome_formatter::write; | ||
| use biome_markdown_syntax::{MarkdownSyntaxKind, MdInlineItalic, MdInlineItalicFields}; | ||
| use biome_rowan::AstNode; | ||
| #[derive(Debug, Clone, Default)] | ||
| pub(crate) struct FormatMdInlineItalic; | ||
| impl FormatNodeRule<MdInlineItalic> for FormatMdInlineItalic { | ||
| fn fmt_fields(&self, node: &MdInlineItalic, f: &mut MarkdownFormatter) -> FormatResult<()> { | ||
| format_verbatim_node(node.syntax()).fmt(f) | ||
| let MdInlineItalicFields { | ||
| l_fence, | ||
| content, | ||
| r_fence, | ||
| } = node.as_fields(); | ||
|
|
||
| let l_fence = l_fence?; | ||
| let r_fence = r_fence?; | ||
|
|
||
| // Nested italic anywhere in the subtree → keep entire node verbatim. | ||
| // Normalizing to `_` could create `__` (bold) adjacency: `_*foo*_` → `__foo__`. | ||
| if node | ||
| .syntax() | ||
| .descendants() | ||
|
tidefield marked this conversation as resolved.
|
||
| .skip(1) | ||
| .any(|d| d.kind() == MarkdownSyntaxKind::MD_INLINE_ITALIC) | ||
| { | ||
| // TODO: instead of format_verbatim_node, pass options to child formatters so | ||
| // other normalizations (bold, code, etc.) still run inside nested italic content. | ||
| // See example-383.md for a case where Prettier handles escapes inside italic. | ||
| return format_verbatim_node(node.syntax()).fmt(f); | ||
|
tidefield marked this conversation as resolved.
|
||
| } | ||
|
|
||
| let prev_is_alphanum = l_fence | ||
| .prev_token() | ||
| .and_then(|t| t.text_trimmed().chars().last()) | ||
| .is_some_and(|c| c.is_alphanumeric()); | ||
| let next_is_alphanum = r_fence | ||
| .next_token() | ||
| .and_then(|t| t.text_trimmed().chars().next()) | ||
| .is_some_and(|c| c.is_alphanumeric()); | ||
|
|
||
| // See https://spec.commonmark.org/0.31.2/#emphasis-and-strong-emphasis | ||
| // Prefer `_` but use `*` when adjacent to alphanumeric | ||
| // For example, `a_b_c` won't parse `b` as italic, but `a*b*c` will). | ||
| let target_kind = if prev_is_alphanum || next_is_alphanum { | ||
| MarkdownSyntaxKind::STAR | ||
| } else { | ||
| MarkdownSyntaxKind::UNDERSCORE | ||
| }; | ||
|
|
||
| write_fence(&l_fence, target_kind, f)?; | ||
| write!(f, [content.format()])?; | ||
| write_fence(&r_fence, target_kind, f) | ||
| } | ||
| } | ||
|
|
||
| /// Write a fence token, reusing it if it already matches the target kind. | ||
| fn write_fence( | ||
| fence: &biome_markdown_syntax::MarkdownSyntaxToken, | ||
| target_kind: MarkdownSyntaxKind, | ||
| f: &mut MarkdownFormatter, | ||
| ) -> FormatResult<()> { | ||
| if fence.kind() == target_kind { | ||
| write!(f, [fence.format()]) | ||
| } else { | ||
| let text = if target_kind == MarkdownSyntaxKind::STAR { | ||
| "*" | ||
| } else { | ||
| "_" | ||
| }; | ||
| write!(f, [format_replaced(fence, &token(text))]) | ||
| } | ||
| } | ||
|
ematipico marked this conversation as resolved.
|
||
46 changes: 42 additions & 4 deletions
46
crates/biome_markdown_formatter/src/markdown/auxiliary/list_marker_prefix.rs
This file contains hidden or 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,48 @@ | ||
| use crate::prelude::*; | ||
| use biome_markdown_syntax::MdListMarkerPrefix; | ||
| use biome_rowan::AstNode; | ||
| use biome_formatter::{FormatRuleWithOptions, write}; | ||
| use biome_markdown_syntax::{MdListMarkerPrefix, MdListMarkerPrefixFields}; | ||
| #[derive(Debug, Clone, Default)] | ||
| pub(crate) struct FormatMdListMarkerPrefix; | ||
| pub(crate) struct FormatMdListMarkerPrefix { | ||
| /// Target marker to replace with (e.g. `"-"`). `None` keeps the original. | ||
| target_marker: Option<&'static str>, | ||
| } | ||
| impl FormatNodeRule<MdListMarkerPrefix> for FormatMdListMarkerPrefix { | ||
| fn fmt_fields(&self, node: &MdListMarkerPrefix, f: &mut MarkdownFormatter) -> FormatResult<()> { | ||
| format_verbatim_node(node.syntax()).fmt(f) | ||
| let MdListMarkerPrefixFields { | ||
| pre_marker_indent, | ||
| marker, | ||
| post_marker_space_token, | ||
| content_indent, | ||
| } = node.as_fields(); | ||
|
|
||
| let marker = marker?; | ||
|
|
||
| write!(f, [pre_marker_indent.format()])?; | ||
| // Note that for `- `, the parser treats the indent as part of the marker, not the content | ||
| // This is a parser bug that causes a regression | ||
| // in crates/biome_markdown_formatter/tests/specs/prettier/markdown/spec/example-242.md.snap | ||
| match self.target_marker { | ||
| Some(target) => write!(f, [format_replaced(&marker, &token(target))])?, | ||
| None => write!(f, [marker.format()])?, | ||
| } | ||
|
|
||
| if let Some(space) = post_marker_space_token { | ||
| write!(f, [space.format()])?; | ||
| } | ||
| write!(f, [content_indent.format()]) | ||
| } | ||
| } | ||
|
|
||
| pub(crate) struct FormatMdListMarkerPrefixOptions { | ||
| /// Target marker to replace with (e.g. `Some("-")`). `None` keeps the original. | ||
| pub(crate) target_marker: Option<&'static str>, | ||
| } | ||
|
|
||
| impl FormatRuleWithOptions<MdListMarkerPrefix> for FormatMdListMarkerPrefix { | ||
| type Options = FormatMdListMarkerPrefixOptions; | ||
|
|
||
| fn with_options(mut self, options: Self::Options) -> Self { | ||
| self.target_marker = options.target_marker; | ||
| self | ||
| } | ||
| } |
This file contains hidden or 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 hidden or 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
9 changes: 9 additions & 0 deletions
9
crates/biome_markdown_formatter/tests/specs/markdown/bullet_list.md
This file contains hidden or 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,9 @@ | ||
| + item with __bold__ and *italic* | ||
|
|
||
| - item with __bold__ and *italic* | ||
|
|
||
| * item with `code` | ||
|
|
||
| * - - - | ||
|
|
||
| * - - -\n __bold__ |
35 changes: 35 additions & 0 deletions
35
crates/biome_markdown_formatter/tests/specs/markdown/bullet_list.md.snap
This file contains hidden or 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,35 @@ | ||
| --- | ||
| source: crates/biome_formatter_test/src/snapshot_builder.rs | ||
| info: markdown/bullet_list.md | ||
| --- | ||
|
|
||
| # Input | ||
|
|
||
| ```md | ||
| + item with __bold__ and *italic* | ||
|
|
||
| - item with __bold__ and *italic* | ||
|
|
||
| * item with `code` | ||
|
|
||
| * - - - | ||
|
|
||
| * - - -\n __bold__ | ||
|
|
||
| ``` | ||
|
|
||
|
|
||
| # Formatted | ||
|
|
||
| ```md | ||
| - item with **bold** and _italic_ | ||
|
|
||
| - item with **bold** and _italic_ | ||
|
|
||
| - item with `code` | ||
|
|
||
| * - - - | ||
|
|
||
| - - - -\n **bold** | ||
|
|
||
| ``` |
This file contains hidden or 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
3 changes: 3 additions & 0 deletions
3
crates/biome_markdown_formatter/tests/specs/markdown/inline_italic.md
This file contains hidden or 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,3 @@ | ||
| a*b*c | ||
|
|
||
| a_b_c |
23 changes: 23 additions & 0 deletions
23
crates/biome_markdown_formatter/tests/specs/markdown/inline_italic.md.snap
This file contains hidden or 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,23 @@ | ||
| --- | ||
| source: crates/biome_formatter_test/src/snapshot_builder.rs | ||
| info: markdown/inline_italic.md | ||
| --- | ||
|
|
||
| # Input | ||
|
|
||
| ```md | ||
| a*b*c | ||
|
|
||
| a_b_c | ||
|
|
||
| ``` | ||
|
|
||
|
|
||
| # Formatted | ||
|
|
||
| ```md | ||
| a*b*c | ||
|
|
||
| a_b_c | ||
|
|
||
| ``` |
This file contains hidden or 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
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.