oxc_markdown_parser/block/
lexical.rs1use crate::options::Constructs;
16use crate::syntax::{self, run_len};
17
18use super::probe::{Start, probe};
19use super::scan;
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum LineStart {
27 Blockquote,
28 SetextUnderline,
30 ThematicBreak,
31 AtxHeading,
32 CodeFence,
33 HtmlBlock,
34 MathFence,
35 Liquid,
36 DirectiveOpener,
38 DirectiveCloser,
40 FootnoteDefinition,
41 ListItem,
42 TableDelimiterRow,
45 TableRow,
47}
48
49pub fn ordered_number(marker: &str) -> Option<u32> {
52 marker.trim_end_matches(['.', ')']).parse().ok()
53}
54
55pub fn line_start(constructs: &Constructs, line: &str, in_paragraph: bool) -> Option<LineStart> {
62 let indent = run_len(line.as_bytes(), b' ');
63 if indent >= 4 || line.as_bytes().get(indent) == Some(&b'\t') {
66 return None;
67 }
68 let tail = &line[indent..];
69 if let Some(start) = probe(constructs, tail, in_paragraph) {
70 return Some(match start {
71 Start::Quote => LineStart::Blockquote,
72 Start::Setext(_) => LineStart::SetextUnderline,
73 Start::ThematicBreak => LineStart::ThematicBreak,
74 Start::Atx { .. } => LineStart::AtxHeading,
75 Start::Fence { .. } => LineStart::CodeFence,
76 Start::Html { .. } => LineStart::HtmlBlock,
77 Start::MathFence { .. } => LineStart::MathFence,
78 Start::Liquid => {
79 let closer = syntax::liquid::open(tail)?;
82 if let Some(after) = syntax::liquid::close(&tail[2..], closer)
83 && !scan::is_blank(&tail[2 + after..])
84 {
85 return None;
86 }
87 LineStart::Liquid
88 }
89 Start::Directive { .. } => LineStart::DirectiveOpener,
90 Start::FootnoteDef { .. } => LineStart::FootnoteDefinition,
91 Start::ListItem { .. } => LineStart::ListItem,
92 Start::TableDelimiter(_) => LineStart::TableDelimiterRow,
93 });
94 }
95 if constructs.container_directive && scan::fence_close(tail, b':', 3) {
99 return Some(LineStart::DirectiveCloser);
100 }
101 if constructs.gfm_table && tail.starts_with('|') {
102 return Some(LineStart::TableRow);
103 }
104 None
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 fn md(line: &str, in_paragraph: bool) -> Option<LineStart> {
112 line_start(&Constructs::markdown(), line, in_paragraph)
113 }
114
115 #[test]
116 fn probe_table_mirrors_engine() {
117 assert_eq!(md("> q", false), Some(LineStart::Blockquote));
118 assert_eq!(md("---", true), Some(LineStart::SetextUnderline));
119 assert_eq!(md("---", false), Some(LineStart::ThematicBreak));
120 assert_eq!(md("# h", false), Some(LineStart::AtxHeading));
121 assert_eq!(md("```rs", false), Some(LineStart::CodeFence));
122 assert_eq!(md("<div>", false), Some(LineStart::HtmlBlock));
123 assert_eq!(md("$$", false), Some(LineStart::MathFence));
124 assert_eq!(md("{% t %}", false), Some(LineStart::Liquid));
125 assert_eq!(md("{% t", false), Some(LineStart::Liquid));
126 assert_eq!(md("{{ t }} text", false), None);
127 assert_eq!(md("[^1]: n", false), Some(LineStart::FootnoteDefinition));
128 assert_eq!(md("- item", false), Some(LineStart::ListItem));
129 assert_eq!(md("plain text", true), None);
130 }
131
132 #[test]
133 fn ordered_numbers() {
134 assert_eq!(ordered_number("007."), Some(7));
135 assert_eq!(ordered_number("999999999)"), Some(999_999_999));
136 assert_eq!(ordered_number("-"), None);
137 }
138
139 #[test]
140 fn covers_prettiers_documented_misses() {
141 assert_eq!(md(":::note", false), Some(LineStart::DirectiveOpener));
143 assert_eq!(md(":::", false), Some(LineStart::DirectiveCloser));
144 assert_eq!(md(":::)", false), None, "an emoticon is paragraph text");
145 assert_eq!(md("| - | - |", true), Some(LineStart::TableDelimiterRow));
146 assert_eq!(md("| a | b |", false), Some(LineStart::TableRow));
147 }
148
149 #[test]
150 fn indent_is_indented_code() {
151 assert_eq!(md(" # not a heading", false), None);
152 assert_eq!(md("\t- not a list", false), None);
153 assert_eq!(md(" \t- not a list", false), None);
154 assert_eq!(md(" - list", false), Some(LineStart::ListItem));
155 }
156
157 #[test]
160 fn construct_gating() {
161 let mut c = Constructs::markdown();
162 c.container_directive = false;
163 c.gfm_table = false;
164 assert_eq!(line_start(&c, ":::", false), None);
165 assert_eq!(line_start(&c, "| a |", false), None);
166 }
167}