rockstar

Finished
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 * - Clearer separation of concerns with dedicated functions for each element type
9 * - More intuitive list handling logic
10 * - Better inline formatting processing
11 * - Cleaner code structure with explicit state management
12 */
13
14/**
15 * Wraps text with HTML tags
16 * @param {string} text - Text to wrap
17 * @param {string} tag - HTML tag to use
18 * @returns {string} Wrapped HTML
19 */
120function wrap(text, tag) {
221 return `<${tag}>${text}</${tag}>`;
322}
423
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);
24/**
25 * Processes bold formatting (__text__ -> <strong>text</strong>)
26 * @param {string} markdown - Markdown text
27 * @returns {string} HTML with bold formatting applied
28 */
29function parseBold(markdown) {
30 // Use greedy matching (.+) to match original behavior exactly
31 return markdown.replace(/__(.+)__/, '<strong>$1</strong>');
1332}
1433
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
34/**
35 * Processes italic formatting (_text_ -> <em>text</em>)
36 * @param {string} markdown - Markdown text
37 * @returns {string} HTML with italic formatting applied
38 */
39function parseItalic(markdown) {
40 // Only replace the first occurrence to match original behavior
41 return markdown.replace(/_(.+)_/, '<em>$1</em>');
1742}
1843
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
44/**
45 * Processes inline formatting (bold and italic)
46 * @param {string} markdown - Markdown text
47 * @returns {string} HTML with inline formatting applied
48 */
49function processInlineFormatting(markdown) {
50 // Process bold first, then italic to handle nested formatting correctly
51 return parseItalic(parseBold(markdown));
2152}
2253
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
54/**
55 * Processes paragraph text (wraps in <p> tags unless in a list)
56 * @param {string} markdown - Markdown text
57 * @param {boolean} inList - Whether we're currently in a list
58 * @returns {string} HTML paragraph or plain text
59 */
60function processParagraphText(markdown, inList) {
61 const formattedText = processInlineFormatting(markdown);
62 return inList ? formattedText : wrap(formattedText, 'p');
3063}
3164
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];
65/**
66 * Parses header markdown (# Header -> <h1>Header</h1>)
67 * @param {string} markdown - Line of markdown text
68 * @returns {Object|null} Parsed header object or null if not a header
69 */
70function parseHeader(markdown) {
71 // Count leading # characters
72 let level = 0;
73 while (level < markdown.length && markdown[level] === '#') {
74 level++;
4375 }
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];
76
77 // Valid headers are 1-6 # characters
78 if (level === 0 || level > 6) {
79 return null;
5080 }
81
82 // Extract header content (skip spaces after #)
83 const content = markdown.substring(level).trimStart();
84 const headerTag = `h${level}`;
85
86 return {
87 html: wrap(content, headerTag),
88 closesList: true
89 };
5190}
5291
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 }
92/**
93 * Parses list item markdown (* item -> <li>item</li>)
94 * @param {string} markdown - Line of markdown text
95 * @param {boolean} currentlyInList - Whether we're already in a list
96 * @returns {Object|null} Parsed list item object or null if not a list item
97 */
98function parseListItem(markdown, currentlyInList) {
99 if (!markdown.startsWith('* ')) {
100 return null;
61101 }
62 return [null, list];
102
103 // Extract list item content
104 const content = markdown.substring(2);
105 const formattedContent = processParagraphText(content, true);
106 const listItemHtml = wrap(formattedContent, 'li');
107
108 return {
109 html: listItemHtml,
110 opensList: !currentlyInList,
111 inList: true
112 };
63113}
64114
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
70 }
115/**
116 * Parses paragraph markdown (regular text)
117 * @param {string} markdown - Line of markdown text
118 * @param {boolean} currentlyInList - Whether we're already in a list
119 * @returns {Object} Parsed paragraph object
120 */
121function parseParagraph(markdown, currentlyInList) {
122 const paragraphHtml = processParagraphText(markdown, false);
123
124 return {
125 html: paragraphHtml,
126 closesList: currentlyInList
127 };
71128}
72129
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);
130/**
131 * Parses a single line of markdown
132 * @param {string} line - Line of markdown text
133 * @param {boolean} currentlyInList - Whether we're currently in a list
134 * @returns {Object} Parsed line result
135 */
136function parseLine(line, currentlyInList) {
137 // Try to parse as header
138 let result = parseHeader(line);
139 if (result) {
140 return result;
80141 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
142
143 // Try to parse as list item
144 result = parseListItem(line, currentlyInList);
145 if (result) {
146 return result;
83147 }
84 return [result, inListAfter];
148
149 // Default to paragraph
150 return parseParagraph(line, currentlyInList);
85151}
86152
87153/**
88 * @param {string} markdown
89 * @returns {string}
154 * Main markdown parser function
155 * @param {string} markdown - Markdown text to parse
156 * @returns {string} Generated HTML
90157 */
91158export function parse(markdown) {
92159 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;
160 let html = '';
161 let inList = false;
162
163 for (const line of lines) {
164 const result = parseLine(line, inList);
165
166 // Handle list opening
167 if (result.opensList) {
168 html += '<ul>';
169 }
170
171 // Handle list closing
172 if (result.closesList) {
173 if (inList) {
174 html += '</ul>';
175 }
176 }
177
178 // Add the parsed content
179 html += result.html;
180
181 // Update list state
182 inList = result.inList || false;
99183 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
184
185 // Close any remaining open list
186 if (inList) {
187 html += '</ul>';
104188 }
189
190 return html;
105191}
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.