great

Finished
1function wrap(text, tag) {
2 return `<${tag}>${text}</${tag}>`;
3}
4
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}>`;
12 return markdown.replace(pattern, replacement);
13}
14
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
21}
22
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
30}
31
32function parseHeader(markdown, list) {
33 let count = 0;
34 for (let i = 0; i < markdown.length; i++) {
35 if (markdown[i] === '#') {
36 count += 1;
37 } else {
38 break;
1/**
2 * A Markdown parser that converts Markdown syntax to HTML.
3 * Refactored for better readability and maintainability.
4 */
5class MarkdownParser {
6 /**
7 * Parses markdown text and converts it to HTML
8 * @param {string} markdown - The markdown text to parse
9 * @returns {string} - The resulting HTML
10 */
11 parse(markdown) {
12 const lines = markdown.split('\n');
13 let html = '';
14 let inList = false;
15
16 for (const line of lines) {
17 const parseResult = this.parseLine(line, inList);
18 html += parseResult.html;
19 inList = parseResult.inList;
20 }
21
22 // Close any open list at the end
23 if (inList) {
24 html += '</ul>';
3925 }
26
27 return html;
4028 }
41 if (count === 0 || count > 6) {
42 return [null, list];
29
30 /**
31 * Parses a single line of markdown
32 * @param {string} line - The line to parse
33 * @param {boolean} inList - Whether we're currently in a list
34 * @returns {Object} - Object containing the parsed HTML and updated list state
35 */
36 parseLine(line, inList) {
37 // Try to parse as header
38 const headerResult = this.parseHeader(line, inList);
39 if (headerResult.matched) {
40 return headerResult;
41 }
42
43 // Try to parse as list item
44 const listItemResult = this.parseListItem(line, inList);
45 if (listItemResult.matched) {
46 return listItemResult;
47 }
48
49 // Parse as paragraph
50 return this.parseParagraph(line, inList);
4351 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
47 return [`</ul>${headerHtml}`, false];
48 } else {
49 return [headerHtml, false];
52
53 /**
54 * Parses header markdown (# ## ### etc.)
55 * @param {string} line - The line to parse
56 * @param {boolean} inList - Whether we're currently in a list
57 * @returns {Object} - Parse result with matched flag, html, and inList state
58 */
59 parseHeader(line, inList) {
60 const headerMatch = line.match(/^(#{1,6})\s(.*)/);
61
62 if (!headerMatch) {
63 return { matched: false, html: '', inList };
64 }
65
66 const level = headerMatch[1].length;
67 const content = headerMatch[2];
68 const headerTag = `h${level}`;
69 const htmlContent = this.parseInline(content);
70
71 if (inList) {
72 // Close list before header
73 return {
74 matched: true,
75 html: `</ul><${headerTag}>${htmlContent}</${headerTag}>`,
76 inList: false
77 };
78 } else {
79 return {
80 matched: true,
81 html: `<${headerTag}>${htmlContent}</${headerTag}>`,
82 inList: false
83 };
84 }
5085 }
51}
52
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];
86
87 /**
88 * Parses list item markdown (* item)
89 * @param {string} line - The line to parse
90 * @param {boolean} inList - Whether we're currently in a list
91 * @returns {Object} - Parse result with matched flag, html, and inList state
92 */
93 parseListItem(line, inList) {
94 if (!line.startsWith('* ')) {
95 return { matched: false, html: '', inList };
96 }
97
98 const content = line.substring(2);
99 const htmlContent = this.parseInline(content);
100
101 if (inList) {
102 // Continue existing list
103 return {
104 matched: true,
105 html: `<li>${htmlContent}</li>`,
106 inList: true
107 };
58108 } else {
59 return [`<ul>${innerHtml}`, true];
109 // Start new list
110 return {
111 matched: true,
112 html: `<ul><li>${htmlContent}</li>`,
113 inList: true
114 };
60115 }
61116 }
62 return [null, list];
63}
64
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
117
118 /**
119 * Parses paragraph markdown
120 * @param {string} line - The line to parse
121 * @param {boolean} inList - Whether we're currently in a list
122 * @returns {Object} - Parse result with matched flag, html, and inList state
123 */
124 parseParagraph(line, inList) {
125 const htmlContent = this.parseInline(line);
126
127 if (inList) {
128 // Close list before paragraph
129 return {
130 matched: true,
131 html: `</ul><p>${htmlContent}</p>`,
132 inList: false
133 };
134 } else {
135 return {
136 matched: true,
137 html: `<p>${htmlContent}</p>`,
138 inList: false
139 };
140 }
70141 }
71}
72
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
142
143 /**
144 * Parses inline markdown elements like bold and italic
145 * @param {string} text - The text to parse
146 * @returns {string} - The parsed HTML
147 */
148 parseInline(text) {
149 return this.parseEmphasis(this.parseBold(text));
77150 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
151
152 /**
153 * Parses bold markdown (__text__)
154 * @param {string} text - The text to parse
155 * @returns {string} - The parsed HTML
156 */
157 parseBold(text) {
158 return text.replace(/__(.+)__/g, '<strong>$1</strong>');
80159 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
160
161 /**
162 * Parses italic markdown (_text_)
163 * @param {string} text - The text to parse
164 * @returns {string} - The parsed HTML
165 */
166 parseEmphasis(text) {
167 return text.replace(/_(.+)_/g, '<em>$1</em>');
83168 }
84 return [result, inListAfter];
85169}
86170
171// Create a singleton instance for the export
172const parser = new MarkdownParser();
173
87174/**
88 * @param {string} markdown
89 * @returns {string}
175 * Parses markdown text and converts it to HTML
176 * @param {string} markdown - The markdown text to parse
177 * @returns {string} - The resulting HTML
90178 */
91179export function parse(markdown) {
92 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;
99 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
104 }
180 return parser.parse(markdown);
105181}
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.