let it go

Finished
1// Refactored Markdown parser with improved readability and maintainability
2
3/**
4 * Wraps text with HTML tags
5 * @param {string} text - Text to wrap
6 * @param {string} tag - HTML tag to use
7 * @returns {string} - Text wrapped in HTML tags
8 */
19function wrap(text, tag) {
210 return `<${tag}>${text}</${tag}>`;
311}
412
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
7}
8
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
13/**
14 * Parses bold text (__) to <strong> tags
15 * Uses the same regex pattern as the original implementation
16 * @param {string} markdown - Markdown text
17 * @returns {string} - HTML with strong tags
18 */
19function parseBold(markdown) {
20 const pattern = new RegExp(`__(.+)__`);
21 const replacement = `<strong>$1</strong>`;
1222 return markdown.replace(pattern, replacement);
1323}
1424
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
25/**
26 * Parses emphasis text (_) to <em> tags
27 * Uses the same regex pattern as the original implementation
28 * @param {string} markdown - Markdown text
29 * @returns {string} - HTML with em tags
30 */
31function parseEmphasis(markdown) {
32 const pattern = new RegExp(`_(.+)_`);
33 const replacement = `<em>$1</em>`;
34 return markdown.replace(pattern, replacement);
2135}
2236
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
37/**
38 * Parses inline formatting (__ for bold, _ for emphasis)
39 * Maintains the same order and behavior as the original implementation
40 * @param {string} markdown - Markdown text
41 * @returns {string} - HTML with inline formatting
42 */
43function parseInlineFormatting(markdown) {
44 // Same order as original: parse_ then parse__
45 return parseEmphasis(parseBold(markdown));
3046}
3147
32function parseHeader(markdown, list) {
33 let count = 0;
48/**
49 * Parses header markdown (# Header) to HTML headers (h1-h6)
50 * @param {string} markdown - Markdown header text
51 * @param {boolean} inList - Whether we're currently in a list
52 * @returns {Object|null} - Object with header HTML and list state, or null if not a header
53 */
54function parseHeader(markdown, inList) {
55 // Count leading # characters
56 let level = 0;
3457 for (let i = 0; i < markdown.length; i++) {
3558 if (markdown[i] === '#') {
36 count += 1;
59 level += 1;
3760 } else {
3861 break;
3962 }
4063 }
41 if (count === 0 || count > 6) {
42 return [null, list];
64
65 // Valid headers have 1-6 # characters
66 if (level === 0 || level > 6) {
67 return null;
4368 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
47 return [`</ul>${headerHtml}`, false];
69
70 // Extract header content (skip # and space)
71 const content = markdown.substring(level + 1);
72 const headerTag = `h${level}`;
73 const headerHtml = wrap(content, headerTag);
74
75 // If we were in a list, close it before the header
76 if (inList) {
77 return {
78 html: `</ul>${headerHtml}`,
79 inList: false
80 };
4881 } else {
49 return [headerHtml, false];
82 return {
83 html: headerHtml,
84 inList: false
85 };
5086 }
5187}
5288
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 }
89/**
90 * Parses list item markdown (* Item) to HTML list items
91 * @param {string} markdown - Markdown list item text
92 * @param {boolean} inList - Whether we're currently in a list
93 * @returns {Object|null} - Object with list item HTML and list state, or null if not a list item
94 */
95function parseListItem(markdown, inList) {
96 if (!markdown.startsWith('*')) {
97 return null;
98 }
99
100 // Extract list item content (skip * and space)
101 const content = markdown.substring(2);
102
103 // Parse inline formatting in content
104 const formattedContent = parseInlineFormatting(content);
105
106 // Create list item HTML
107 const listItemHtml = wrap(formattedContent, 'li');
108
109 // Handle list opening/closing
110 if (inList) {
111 return {
112 html: listItemHtml,
113 inList: true
114 };
115 } else {
116 return {
117 html: `<ul>${listItemHtml}`,
118 inList: true
119 };
61120 }
62 return [null, list];
63121}
64122
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
123/**
124 * Parses paragraph markdown to HTML paragraphs
125 * @param {string} markdown - Markdown paragraph text
126 * @param {boolean} inList - Whether we're currently in a list
127 * @returns {Object} - Object with paragraph HTML and list state
128 */
129function parseParagraph(markdown, inList) {
130 // Parse inline formatting in content
131 const formattedContent = parseInlineFormatting(markdown);
132
133 if (inList) {
134 return {
135 html: `</ul>${wrap(formattedContent, 'p')}`,
136 inList: false
137 };
68138 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
139 return {
140 html: wrap(formattedContent, 'p'),
141 inList: false
142 };
70143 }
71144}
72145
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
77 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
146/**
147 * Parses a single line of markdown
148 * @param {string} line - A line of markdown text
149 * @param {boolean} inList - Whether we're currently in a list
150 * @returns {Object} - Object with parsed HTML and updated list state
151 */
152function parseLine(line, inList) {
153 // Try to parse as header
154 const headerResult = parseHeader(line, inList);
155 if (headerResult) {
156 return headerResult;
80157 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
158
159 // Try to parse as list item
160 const listItemResult = parseListItem(line, inList);
161 if (listItemResult) {
162 return listItemResult;
83163 }
84 return [result, inListAfter];
164
165 // Parse as paragraph (default)
166 return parseParagraph(line, inList);
85167}
86168
87169/**
88 * @param {string} markdown
89 * @returns {string}
170 * Parses markdown text to HTML
171 * @param {string} markdown - Markdown text to parse
172 * @returns {string} - HTML representation of the markdown
90173 */
91174export function parse(markdown) {
92175 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;
176 let html = '';
177 let inList = false;
178
179 // Process each line
180 for (const line of lines) {
181 const result = parseLine(line, inList);
182 html += result.html;
183 inList = result.inList;
99184 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
185
186 // Close any open list at the end
187 if (inList) {
188 html += '</ul>';
104189 }
190
191 return html;
105192}
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.