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
34 changes: 25 additions & 9 deletions crates/biome_markdown_factory/src/generated/node_factory.rs

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

24 changes: 23 additions & 1 deletion crates/biome_markdown_factory/src/generated/syntax_factory.rs

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

63 changes: 63 additions & 0 deletions crates/biome_markdown_formatter/src/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,44 @@ impl IntoFormat<MarkdownFormatContext> for biome_markdown_syntax::MdQuote {
)
}
}
impl FormatRule<biome_markdown_syntax::MdQuoteIndent>
for crate::markdown::auxiliary::quote_indent::FormatMdQuoteIndent
{
type Context = MarkdownFormatContext;
#[inline(always)]
fn fmt(
&self,
node: &biome_markdown_syntax::MdQuoteIndent,
f: &mut MarkdownFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_markdown_syntax::MdQuoteIndent>::fmt(self, node, f)
}
}
impl AsFormat<MarkdownFormatContext> for biome_markdown_syntax::MdQuoteIndent {
type Format<'a> = FormatRefWithRule<
'a,
biome_markdown_syntax::MdQuoteIndent,
crate::markdown::auxiliary::quote_indent::FormatMdQuoteIndent,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::markdown::auxiliary::quote_indent::FormatMdQuoteIndent::default(),
)
}
}
impl IntoFormat<MarkdownFormatContext> for biome_markdown_syntax::MdQuoteIndent {
type Format = FormatOwnedWithRule<
biome_markdown_syntax::MdQuoteIndent,
crate::markdown::auxiliary::quote_indent::FormatMdQuoteIndent,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::markdown::auxiliary::quote_indent::FormatMdQuoteIndent::default(),
)
}
}
impl FormatRule<biome_markdown_syntax::MdQuotePrefix>
for crate::markdown::auxiliary::quote_prefix::FormatMdQuotePrefix
{
Expand Down Expand Up @@ -1454,6 +1492,31 @@ impl IntoFormat<MarkdownFormatContext> for biome_markdown_syntax::MdInlineItemLi
)
}
}
impl AsFormat<MarkdownFormatContext> for biome_markdown_syntax::MdQuoteIndentList {
type Format<'a> = FormatRefWithRule<
'a,
biome_markdown_syntax::MdQuoteIndentList,
crate::markdown::lists::quote_indent_list::FormatMdQuoteIndentList,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::markdown::lists::quote_indent_list::FormatMdQuoteIndentList::default(),
)
}
}
impl IntoFormat<MarkdownFormatContext> for biome_markdown_syntax::MdQuoteIndentList {
type Format = FormatOwnedWithRule<
biome_markdown_syntax::MdQuoteIndentList,
crate::markdown::lists::quote_indent_list::FormatMdQuoteIndentList,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::markdown::lists::quote_indent_list::FormatMdQuoteIndentList::default(),
)
}
}
impl FormatRule<biome_markdown_syntax::MdBogus> for crate::markdown::bogus::bogus::FormatMdBogus {
type Context = MarkdownFormatContext;
#[inline(always)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub(crate) mod newline;
pub(crate) mod ordered_list_item;
pub(crate) mod paragraph;
pub(crate) mod quote;
pub(crate) mod quote_indent;
pub(crate) mod quote_prefix;
pub(crate) mod reference_image;
pub(crate) mod reference_link;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use crate::prelude::*;
use biome_markdown_syntax::MdQuoteIndent;
use biome_rowan::AstNode;
#[derive(Debug, Clone, Default)]
pub(crate) struct FormatMdQuoteIndent;
impl FormatNodeRule<MdQuoteIndent> for FormatMdQuoteIndent {
fn fmt_fields(&self, node: &MdQuoteIndent, f: &mut MarkdownFormatter) -> FormatResult<()> {
format_verbatim_node(node.syntax()).fmt(f)
}
}
1 change: 1 addition & 0 deletions crates/biome_markdown_formatter/src/markdown/lists/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pub(crate) mod bullet_list;
pub(crate) mod code_name_list;
pub(crate) mod hash_list;
pub(crate) mod inline_item_list;
pub(crate) mod quote_indent_list;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use crate::prelude::*;
use biome_markdown_syntax::MdQuoteIndentList;
#[derive(Debug, Clone, Default)]
pub(crate) struct FormatMdQuoteIndentList;
impl FormatRule<MdQuoteIndentList> for FormatMdQuoteIndentList {
type Context = MarkdownFormatContext;
fn fmt(&self, node: &MdQuoteIndentList, f: &mut MarkdownFormatter) -> FormatResult<()> {
f.join().entries(node.iter().formatted()).finish()
}
}
3 changes: 3 additions & 0 deletions crates/biome_markdown_parser/src/syntax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ pub(crate) const MAX_LINK_DESTINATION_PAREN_DEPTH: i32 = 32;
const INDENT_CODE_BLOCK_SPACES: usize = 4;
/// Tabs advance to the next 4-space tab stop in CommonMark parsing.
const TAB_STOP_SPACES: usize = 4;
/// CommonMark allows 0-3 spaces of optional indentation before block-level
/// markers (blockquotes §5.1, list items §5.2/§5.3, ATX headings §4.2, etc.).
pub(crate) const MAX_BLOCK_PREFIX_INDENT: usize = 3;

pub(crate) fn parse_document(p: &mut MarkdownParser) {
let m = p.start();
Expand Down
40 changes: 32 additions & 8 deletions crates/biome_markdown_parser/src/syntax/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ use biome_parser::prelude::ParsedSyntax::{self, *};
use crate::MarkdownParser;
use crate::syntax::parse_any_block_with_indent_code_policy;
use crate::syntax::parse_error::quote_nesting_too_deep;
use crate::syntax::{INDENT_CODE_BLOCK_SPACES, TAB_STOP_SPACES, is_paragraph_like};
use crate::syntax::{
INDENT_CODE_BLOCK_SPACES, MAX_BLOCK_PREFIX_INDENT, TAB_STOP_SPACES, is_paragraph_like,
};

/// Check if we're at the start of a block quote (`>`).
pub(crate) fn at_quote(p: &mut MarkdownParser) -> bool {
Expand All @@ -55,10 +57,10 @@ pub(crate) fn at_quote(p: &mut MarkdownParser) -> bool {
// Treat virtual line start as column 0.
indent = 0;
}
if indent > 3 {
if indent > MAX_BLOCK_PREFIX_INDENT {
return false;
}
p.skip_line_indent(3);
p.skip_line_indent(MAX_BLOCK_PREFIX_INDENT);
p.at(T![>])
})
}
Expand All @@ -80,7 +82,7 @@ pub(crate) fn parse_quote(p: &mut MarkdownParser) -> ParsedSyntax {
let range = p.cur_range();
p.error(quote_nesting_too_deep(p, range, max_nesting_depth));
p.state_mut().quote_depth_exceeded = true;
p.skip_line_indent(3);
p.skip_line_indent(MAX_BLOCK_PREFIX_INDENT);
if p.at(T![>]) {
p.parse_as_skipped_trivia_tokens(|p| p.bump(T![>]));
} else if p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == ">" {
Expand Down Expand Up @@ -127,16 +129,38 @@ fn emit_quote_prefix_node(p: &mut MarkdownParser) -> bool {
///
/// Returns whether a post-marker separator was consumed.
fn emit_quote_prefix_tokens(p: &mut MarkdownParser, use_virtual_line_start: bool) -> Option<bool> {
// TODO: Emit MD_QUOTE_PRE_MARKER_INDENT directly instead of skipping indent trivia.
// This is intentionally deferred from Step 1b to keep parser migration scope narrow.
let saved_virtual = if use_virtual_line_start {
let prev = p.state().virtual_line_start;
p.state_mut().virtual_line_start = Some(p.cur_range().start());
Some(prev)
} else {
None
};
p.skip_line_indent(3);

// Direct bounded scan (0-3 cols per CommonMark §5.1): simpler than ParseNodeList
// here because we immediately validate `>` and keep this path no-recovery.
let indent_list_m = p.start();
let mut consumed = 0usize;
while p.at(MD_TEXTUAL_LITERAL) {
let text = p.cur_text();
if text.is_empty() || !text.chars().all(|c| c == ' ' || c == '\t') {
break;
}
// Tabs expand to tab-stop width (CommonMark §2.2).
let indent: usize = text
.chars()
.map(|c| if c == '\t' { TAB_STOP_SPACES } else { 1 })
.sum();
if consumed + indent > MAX_BLOCK_PREFIX_INDENT {
break;
}
consumed += indent;
let indent_m = p.start();
p.bump_remap(MD_QUOTE_PRE_MARKER_INDENT);
indent_m.complete(p, MD_QUOTE_INDENT);
}
indent_list_m.complete(p, MD_QUOTE_INDENT_LIST);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason why we don't use ParseList for this? Just curious

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking: this is a tiny bounded scan (<= 3 cols per CommonMark’s 0–3 indent before >), immediately followed by > validation, so a direct loop felt simpler than ParseNodeList. It also keeps this path strictly no-recovery/no-diagnostic.

Happy to switch to ParseNodeList if you prefer consistency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think it's fine for now. Maybe can you leave a comment explaning the reasoning

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


if let Some(prev) = saved_virtual {
p.state_mut().virtual_line_start = prev;
}
Expand Down Expand Up @@ -311,7 +335,7 @@ pub(crate) fn line_has_quote_prefix_at_current(p: &MarkdownParser, depth: usize)
}
_ => break,
}
if indent > 3 {
if indent > MAX_BLOCK_PREFIX_INDENT {
return false;
}
}
Expand Down
Loading
Loading