Finished
1function wrap(text, tag) {
2 return `<${tag}>${text}</${tag}>`;
3}
1/**
2 * Refactored Markdown Parser
3 *
4 * This refactored version maintains the exact same functionality as the original
5 * but with improved readability and maintainability.
6 *
7 * Key improvements:
8 * - Class-based structure for better state management
9 * - Clear separation of concerns with well-named methods
10 * - Elimination of complex array return values
11 * - More explicit control flow
12 * - Better variable names and documentation
13 */
414
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
7}
15class MarkdownParser {
16 constructor() {
17 this.inList = false;
18 this.result = '';
19 }
820
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}
21 /**
22 * Wraps text with HTML tags
23 * @param {string} text - Text to wrap
24 * @param {string} tag - HTML tag to use
25 * @returns {string} Wrapped HTML
26 */
27 wrap(text, tag) {
28 return `<${tag}>${text}</${tag}>`;
29 }
1430
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
31 /**
32 * Parses bold text (double underscores)
33 * @param {string} markdown - Markdown text
34 * @returns {string} HTML with <strong> tags
35 */
36 parseBold(markdown) {
37 const pattern = /__(.+)__/;
38 const replacement = '<strong>$1</strong>';
39 return markdown.replace(pattern, replacement);
40 }
1841
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
21}
42 /**
43 * Parses italic text (single underscores)
44 * @param {string} markdown - Markdown text
45 * @returns {string} HTML with <em> tags
46 */
47 parseItalic(markdown) {
48 const pattern = /_(.+)_/;
49 const replacement = '<em>$1</em>';
50 return markdown.replace(pattern, replacement);
51 }
2252
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
53 /**
54 * Parses inline formatting (bold and italic)
55 * @param {string} markdown - Markdown text
56 * @returns {string} HTML with formatting tags
57 */
58 parseInlineFormatting(markdown) {
59 return this.parseItalic(this.parseBold(markdown));
2960 }
30}
3161
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;
62 /**
63 * Parses header markdown (# ## ### etc.)
64 * @param {string} line - Line of markdown text
65 * @returns {string|null} HTML header or null if not a header
66 */
67 parseHeader(line) {
68 let hashCount = 0;
69 for (let i = 0; i < line.length; i++) {
70 if (line[i] === '#') {
71 hashCount += 1;
72 } else {
73 break;
74 }
3975 }
76
77 // Headers must have 1-6 hash characters
78 if (hashCount === 0 || hashCount > 6) {
79 return null;
80 }
81
82 const headerTag = `h${hashCount}`;
83 const headerText = line.substring(hashCount + 1);
84 const headerHtml = this.wrap(headerText, headerTag);
85
86 // Close list if we're transitioning from list to header
87 if (this.inList) {
88 this.inList = false;
89 return `</ul>${headerHtml}`;
90 }
91
92 return headerHtml;
4093 }
41 if (count === 0 || count > 6) {
42 return [null, list];
43 }
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];
50 }
51}
5294
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];
95 /**
96 * Parses list item markdown (* item)
97 * @param {string} line - Line of markdown text
98 * @returns {string|null} HTML list item or null if not a list item
99 */
100 parseListItem(line) {
101 if (!line.startsWith('* ')) {
102 return null;
103 }
104
105 const itemText = line.substring(2);
106 const formattedText = this.parseInlineFormatting(itemText);
107 const listItemHtml = this.wrap(formattedText, 'li');
108
109 if (!this.inList) {
110 // Starting a new list
111 this.inList = true;
112 return `<ul>${listItemHtml}`;
58113 } else {
59 return [`<ul>${innerHtml}`, true];
114 // Continuing existing list
115 return listItemHtml;
60116 }
61117 }
62 return [null, list];
63}
64118
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
119 /**
120 * Parses paragraph markdown (default case)
121 * @param {string} line - Line of markdown text
122 * @returns {string} HTML paragraph
123 */
124 parseParagraph(line) {
125 const formattedText = this.parseInlineFormatting(line);
126
127 // Close list if we're transitioning from list to paragraph
128 if (this.inList) {
129 this.inList = false;
130 return `</ul>${this.wrap(formattedText, 'p')}`;
131 }
132
133 return this.wrap(formattedText, 'p');
70134 }
71}
72135
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);
136 /**
137 * Parses a single line of markdown
138 * @param {string} line - Line of markdown text
139 * @returns {string} HTML output for the line
140 */
141 parseLine(line) {
142 // Try to parse as header
143 const headerResult = this.parseHeader(line);
144 if (headerResult !== null) {
145 return headerResult;
146 }
147
148 // Try to parse as list item
149 const listItemResult = this.parseListItem(line);
150 if (listItemResult !== null) {
151 return listItemResult;
152 }
153
154 // Default to paragraph
155 return this.parseParagraph(line);
80156 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
157
158 /**
159 * Parses complete markdown text
160 * @param {string} markdown - Complete markdown text
161 * @returns {string} Complete HTML output
162 */
163 parse(markdown) {
164 this.inList = false;
165 this.result = '';
166
167 const lines = markdown.split('\n');
168
169 for (const line of lines) {
170 this.result += this.parseLine(line);
171 }
172
173 // Close any open list at the end
174 if (this.inList) {
175 this.result += '</ul>';
176 }
177
178 return this.result;
83179 }
84 return [result, inListAfter];
85180}
86181
87182/**
88 * @param {string} markdown
89 * @returns {string}
183 * Parses markdown text and returns HTML
184 * @param {string} markdown - Markdown text to parse
185 * @returns {string} HTML output
90186 */
91187export 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 }
188 const parser = new MarkdownParser();
189 return parser.parse(markdown);
105190}
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.