chris

Finished
1/**
2 * Refactored Markdown Parser
3 *
4 * This refactored version improves readability and maintainability while preserving
5 * all existing functionality. Key improvements:
6 * 1. Centralized list state management
7 * 2. Cleaner separation of concerns
8 * 3. Elimination of empty paragraph generation
9 * 4. More intuitive code structure
10 */
11
12/**
13 * Wraps text with HTML tags
14 * @param {string} text - Text to wrap
15 * @param {string} tag - HTML tag to use
16 * @returns {string} Wrapped HTML
17 */
118function wrap(text, tag) {
219 return `<${tag}>${text}</${tag}>`;
320}
421
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
7}
8
9function parser(markdown, delimiter, tag) {
22/**
23 * Generic parser for inline formatting using delimiters
24 * @param {string} markdown - Markdown text
25 * @param {string} delimiter - The delimiter to replace (e.g., '__', '_')
26 * @param {string} tag - HTML tag to use (e.g., 'strong', 'em')
27 * @returns {string} HTML with formatted tags
28 */
29function parseInline(markdown, delimiter, tag) {
1030 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
1131 const replacement = `<${tag}>$1</${tag}>`;
1232 return markdown.replace(pattern, replacement);
1333}
1434
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
35/**
36 * Parses bold text (__) to <strong> tags
37 * @param {string} markdown - Markdown text
38 * @returns {string} HTML with <strong> tags
39 */
40function parseBold(markdown) {
41 return parseInline(markdown, '__', 'strong');
1742}
1843
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
44/**
45 * Parses italic text (_) to <em> tags
46 * @param {string} markdown - Markdown text
47 * @returns {string} HTML with <em> tags
48 */
49function parseItalic(markdown) {
50 return parseInline(markdown, '_', 'em');
2151}
2252
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
53/**
54 * Parses inline formatting (__ for bold, _ for italic)
55 * @param {string} text - Text to format
56 * @returns {string} Formatted HTML
57 */
58function parseInlineFormatting(text) {
59 // Parse bold first, then italic - this matches the original implementation
60 return parseItalic(parseBold(text));
3061}
3162
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;
39 }
40 }
41 if (count === 0 || count > 6) {
42 return [null, list];
63/**
64 * Parses a header line (#, ##, etc.)
65 * @param {string} line - Line to parse
66 * @returns {string|null} HTML header or null if not a header
67 */
68function parseHeader(line) {
69 // Count leading # characters
70 let level = 0;
71 while (level < line.length && line[level] === '#') {
72 level++;
4373 }
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];
74
75 // Valid headers have 1-6 # characters followed by a space
76 if (level === 0 || level > 6 || line[level] !== ' ') {
77 return null;
5078 }
79
80 const content = line.substring(level + 1);
81 const headerTag = `h${level}`;
82 return wrap(parseInlineFormatting(content), headerTag);
5183}
5284
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 }
61 }
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];
85/**
86 * Parses a list item line (* item)
87 * @param {string} line - Line to parse
88 * @returns {string|null} HTML list item or null if not a list item
89 */
90function parseListItem(line) {
91 if (line.startsWith('* ')) {
92 const content = line.substring(2);
93 return wrap(parseInlineFormatting(content), 'li');
7094 }
95 return null;
7196}
7297
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);
80 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
83 }
84 return [result, inListAfter];
98/**
99 * Parses a paragraph line (plain text)
100 * @param {string} line - Line to parse
101 * @returns {string} HTML paragraph
102 */
103function parseParagraph(line) {
104 return wrap(parseInlineFormatting(line), 'p');
85105}
86106
87107/**
88 * @param {string} markdown
89 * @returns {string}
108 * Main parsing function that converts Markdown to HTML
109 * @param {string} markdown - Markdown text to parse
110 * @returns {string} HTML output
90111 */
91112export function parse(markdown) {
92113 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;
114 let html = '';
115 let inList = false;
116
117 for (const line of lines) {
118 // Handle headers
119 const headerHtml = parseHeader(line);
120 if (headerHtml) {
121 // Close list if we were in one
122 if (inList) {
123 html += '</ul>';
124 inList = false;
125 }
126 html += headerHtml;
127 continue;
128 }
129
130 // Handle list items
131 const listItemHtml = parseListItem(line);
132 if (listItemHtml) {
133 // Open list if we weren't in one
134 if (!inList) {
135 html += '<ul>';
136 inList = true;
137 }
138 html += listItemHtml;
139 continue;
140 }
141
142 // Close list if we were in one
143 if (inList) {
144 html += '</ul>';;
145 inList = false;
146 }
147
148 // Handle paragraphs (but skip empty lines)
149 if (line.trim() !== '') {
150 html += parseParagraph(line);
151 }
99152 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
153
154 // Close any open list at the end
155 if (inList) {
156 html += '</ul>';
104157 }
158
159 return html;
105160}
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.