rockstar

Finished
1/**
2 * Refactored Markdown Parser
3 *
4 * This refactored version improves readability and maintainability while
5 * preserving all existing functionality.
6 */
7
8// Helper function to wrap text in HTML tags
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// Parse bold text (__text__ -> <strong>text</strong>)
14function parseBold(markdown) {
15 const pattern = /__(.+)__/;
16 const replacement = '<strong>$1</strong>';
1217 return markdown.replace(pattern, replacement);
1318}
1419
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
20// Parse italic text (_text_ -> <em>text</em>)
21function parseItalic(markdown) {
22 const pattern = /_(.+)_/;
23 const replacement = '<em>$1</em>';
24 return markdown.replace(pattern, replacement);
1725}
1826
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
27// Parse inline formatting (bold and italic)
28function parseInlineFormatting(markdown) {
29 return parseItalic(parseBold(markdown));
2130}
2231
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
32// Parse text content, optionally wrapping in paragraph tags
33function parseText(markdown, inList = false) {
34 const formattedText = parseInlineFormatting(markdown);
35 return inList ? formattedText : wrap(formattedText, 'p');
3036}
3137
32function parseHeader(markdown, list) {
33 let count = 0;
38// Parse header (# Header -> <h1>Header</h1>)
39function parseHeader(markdown, currentlyInList) {
40 // Count leading # characters
41 let headerLevel = 0;
3442 for (let i = 0; i < markdown.length; i++) {
3543 if (markdown[i] === '#') {
36 count += 1;
44 headerLevel++;
3745 } else {
3846 break;
3947 }
4048 }
41 if (count === 0 || count > 6) {
42 return [null, list];
49
50 // Valid headers have 1-6 # characters
51 if (headerLevel === 0 || headerLevel > 6) {
52 return [null, currentlyInList];
4353 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
54
55 // Extract header content (skip # characters and space)
56 const headerContent = markdown.substring(headerLevel + 1);
57 const headerTag = `h${headerLevel}`;
58 const headerHtml = wrap(headerContent, headerTag);
59
60 // If we were in a list, close it before the header
61 if (currentlyInList) {
4762 return [`</ul>${headerHtml}`, false];
48 } else {
49 return [headerHtml, false];
5063 }
64
65 return [headerHtml, false];
5166}
5267
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 }
68// Parse list items (* item -> <ul><li>item</li>)
69function parseListItem(markdown, currentlyInList) {
70 if (!markdown.startsWith('*')) {
71 return [null, currentlyInList];
72 }
73
74 // Extract list item content (skip '* ' prefix)
75 const itemContent = markdown.substring(2);
76 const listItemHtml = wrap(parseText(itemContent, true), 'li');
77
78 // If we're already in a list, just add the list item
79 // Otherwise, start a new list
80 if (currentlyInList) {
81 return [listItemHtml, true];
82 } else {
83 return [`<ul>${listItemHtml}`, true];
6184 }
62 return [null, list];
6385}
6486
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
87// Parse paragraph content
88function parseParagraph(markdown, currentlyInList) {
89 // If we were in a list, close it before the paragraph
90 if (currentlyInList) {
6991 return [`</ul>${parseText(markdown, false)}`, false];
7092 }
93
94 return [parseText(markdown, false), false];
7195}
7296
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
97// Parse a single line of markdown
98function parseLine(markdown, currentlyInList) {
99 // Try parsing as header first
100 let [result, newListState] = parseHeader(markdown, currentlyInList);
101
102 // If not a header, try parsing as list item
75103 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
104 [result, newListState] = parseListItem(markdown, currentlyInList);
77105 }
106
107 // If not a list item, parse as paragraph
78108 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
109 [result, newListState] = parseParagraph(markdown, currentlyInList);
80110 }
111
112 // This should never happen with valid input
81113 if (result === null) {
82 throw new Error('Remove this line and implement the function');
114 throw new Error('Invalid markdown line');
83115 }
84 return [result, inListAfter];
116
117 return [result, newListState];
85118}
86119
87120/**
88 * @param {string} markdown
89 * @returns {string}
121 * Parse markdown text and convert to HTML
122 * @param {string} markdown - The markdown text to parse
123 * @returns {string} - The resulting HTML
90124 */
91125export function parse(markdown) {
92126 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;
127 let htmlResult = '';
128 let inList = false;
129
130 // Process each line
131 for (const line of lines) {
132 const [lineHtml, newListState] = parseLine(line, inList);
133 htmlResult += lineHtml;
134 inList = newListState;
99135 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
136
137 // Close any open list at the end
138 if (inList) {
139 htmlResult += '</ul>';
104140 }
141
142 return htmlResult;
105143}
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.