Merry

Finished
1function wrap(text, tag) {
2 return `<${tag}>${text}</${tag}>`;
3}
4
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
7}
1/**
2 * Refactored Markdown Parser
3 *
4 * This refactored version maintains the exact same functionality as the original
5 * while improving readability and maintainability.
6 *
7 * Key improvements:
8 * 1. Clearer separation of concerns with dedicated functions for each element type
9 * 2. More descriptive variable and function names
10 * 3. Simplified logic flow with early returns
11 * 4. Better organization of parsing logic
12 * 5. Comprehensive comments explaining the parsing process
13 */
814
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
12 return markdown.replace(pattern, replacement);
15/**
16 * Wraps text with HTML tags
17 * @param {string} text - The text to wrap
18 * @param {string} tag - The HTML tag to use
19 * @returns {string} The wrapped text
20 */
21function wrapText(text, tag) {
22 return `<${tag}>${text}</${tag}>`;
1323}
1424
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
25/**
26 * Parses bold text (surrounded by double underscores)
27 * @param {string} markdown - The markdown text to parse
28 * @returns {string} HTML with bold formatting
29 */
30function parseBold(markdown) {
31 // Match text surrounded by __ (non-greedy to handle nested formatting correctly)
32 const boldPattern = /__(.+)__/;
33 return markdown.replace(boldPattern, '<strong>$1</strong>');
1734}
1835
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
36/**
37 * Parses italic text (surrounded by single underscores)
38 * @param {string} markdown - The markdown text to parse
39 * @returns {string} HTML with italic formatting
40 */
41function parseItalic(markdown) {
42 // Match text surrounded by _ (non-greedy to handle nested formatting correctly)
43 const italicPattern = /_(.+)_/;
44 return markdown.replace(italicPattern, '<em>$1</em>');
2145}
2246
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
47/**
48 * Parses inline formatting (bold and italic)
49 * IMPORTANT: Parse bold BEFORE italic to handle nested formatting correctly
50 * @param {string} text - The text to format
51 * @param {boolean} inList - Whether we're inside a list
52 * @returns {string} Formatted text
53 */
54function parseInlineFormatting(text, inList) {
55 // Apply bold formatting first, then italic formatting
56 // This order is crucial for handling nested formatting correctly
57 const formattedText = parseItalic(parseBold(text));
58
59 // If we're not in a list, wrap in paragraph tags
60 if (!inList) {
61 return wrapText(formattedText, 'p');
2962 }
63
64 return formattedText;
3065}
3166
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
67/**
68 * Parses a header (lines starting with #)
69 * @param {string} line - The line to parse
70 * @param {boolean} currentlyInList - Whether we're currently in a list
71 * @returns {[string|null, boolean]} [HTML result or null if not a header, new list state]
72 */
73function parseHeader(line, currentlyInList) {
74 // Count the number of # characters at the start of the line
75 let headerLevel = 0;
76 for (let i = 0; i < line.length; i++) {
77 if (line[i] === '#') {
78 headerLevel++;
3779 } else {
3880 break;
3981 }
4082 }
41 if (count === 0 || count > 6) {
42 return [null, list];
83
84 // Valid headers have 1-6 # characters
85 if (headerLevel === 0 || headerLevel > 6) {
86 return [null, currentlyInList];
4387 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
88
89 // Extract the header content (skip the # characters and the following space)
90 const headerContent = line.substring(headerLevel + 1);
91 const headerTag = `h${headerLevel}`;
92 const headerHtml = wrapText(headerContent, headerTag);
93
94 // If we were in a list, close it before the header
95 if (currentlyInList) {
4796 return [`</ul>${headerHtml}`, false];
48 } else {
49 return [headerHtml, false];
5097 }
98
99 return [headerHtml, false];
51100}
52101
53function parseLineItem(markdown, list) {
54 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
58 } else {
59 return [`<ul>${innerHtml}`, true];
60 }
102/**
103 * Parses a list item (lines starting with *)
104 * @param {string} line - The line to parse
105 * @param {boolean} currentlyInList - Whether we're currently in a list
106 * @returns {[string|null, boolean]} [HTML result or null if not a list item, new list state]
107 */
108function parseListItem(line, currentlyInList) {
109 // Check if this is a list item
110 if (!line.startsWith('*')) {
111 return [null, currentlyInList];
61112 }
62 return [null, list];
63}
64
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
113
114 // Extract the list item content (skip the '* ' prefix)
115 const itemContent = line.substring(2);
116
117 // Parse inline formatting for the list item content
118 const formattedContent = parseInlineFormatting(itemContent, true);
119
120 // Wrap in list item tags
121 const listItemHtml = wrapText(formattedContent, 'li');
122
123 // Handle list opening/closing
124 if (currentlyInList) {
125 // Already in a list, just add the item
126 return [listItemHtml, true];
68127 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
128 // Starting a new list, open the ul tag
129 return [`<ul>${listItemHtml}`, true];
70130 }
71131}
72132
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
133/**
134 * Parses a paragraph (default case for regular text)
135 * @param {string} line - The line to parse
136 * @param {boolean} currentlyInList - Whether we're currently in a list
137 * @returns {[string, boolean]} [HTML result, new list state]
138 */
139function parseParagraph(line, currentlyInList) {
140 // Parse inline formatting
141 const paragraphHtml = parseInlineFormatting(line, false);
142
143 // If we were in a list, close it before the paragraph
144 if (currentlyInList) {
145 return [`</ul>${paragraphHtml}`, false];
77146 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
147
148 return [paragraphHtml, false];
149}
150
151/**
152 * Parses a single line of markdown
153 * @param {string} line - The line to parse
154 * @param {boolean} currentlyInList - Whether we're currently in a list
155 * @returns {[string, boolean]} [HTML result, new list state]
156 */
157function parseLine(line, currentlyInList) {
158 // Try to parse as header first
159 let [result, newListState] = parseHeader(line, currentlyInList);
160 if (result !== null) {
161 return [result, newListState];
80162 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
163
164 // Try to parse as list item
165 [result, newListState] = parseListItem(line, currentlyInList);
166 if (result !== null) {
167 return [result, newListState];
83168 }
84 return [result, inListAfter];
169
170 // Default to paragraph
171 return parseParagraph(line, currentlyInList);
85172}
86173
87174/**
88 * @param {string} markdown
89 * @returns {string}
175 * Main function to parse markdown text into HTML
176 * @param {string} markdown - The markdown text to parse
177 * @returns {string} The resulting HTML
90178 */
91179export function parse(markdown) {
180 // Split the markdown into lines
92181 const lines = markdown.split('\n');
93 let result = '';
94 let list = false;
95 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
97 result += lineResult;
98 list = newList;
182
183 // Process each line and build the result
184 let htmlResult = '';
185 let inList = false;
186
187 for (const line of lines) {
188 const [lineHtml, newListState] = parseLine(line, inList);
189 htmlResult += lineHtml;
190 inList = newListState;
99191 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
192
193 // Close any open list at the end
194 if (inList) {
195 htmlResult += '</ul>';
104196 }
197
198 return htmlResult;
105199}
Test NameStatus
parses normal text as a paragraph
Pass
parsing italics
Pass
parsing bold text
Pass
mixed normal, italics and bold text
Pass
with h1 header level
Pass
with h2 header level
Pass
with h3 header level
Pass
with h4 header level
Pass
with h5 header level
Pass
with h6 header level
Pass
with h7 header level
Pass
unordered lists
Pass
with a little bit of everything
Pass
with markdown symbols in the header text that should not be interpreted
Pass
with markdown symbols in the list item text that should not be interpreted
Pass
with markdown symbols in the paragraph text that should not be interpreted
Pass
unordered lists close properly with preceding and following lines
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.