rockstar

Finished
1/**
2 * Refactored Markdown Parser
3 *
4 * This refactored version improves readability and maintainability while
5 * preserving the exact same parsing behavior as the original implementation.
6 */
7
8/**
9 * Wraps text with HTML tags
10 * @param {string} text - The text to wrap
11 * @param {string} tag - The HTML tag to use
12 * @returns {string} The wrapped text
13 */
114function wrap(text, tag) {
215 return `<${tag}>${text}</${tag}>`;
316}
417
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}>`;
18/**
19 * Parses bold text (double underscore)
20 * @param {string} markdown - The markdown text to parse
21 * @returns {string} The parsed HTML
22 */
23function parseBold(markdown) {
24 const pattern = new RegExp(`__([^_]+)__`);
25 const replacement = `<strong>$1</strong>`;
1226 return markdown.replace(pattern, replacement);
1327}
1428
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
29/**
30 * Parses italic text (single underscore)
31 * @param {string} markdown - The markdown text to parse
32 * @returns {string} The parsed HTML
33 */
34function parseItalic(markdown) {
35 // Use a more specific pattern to avoid matching bold markers
36 const pattern = new RegExp(`_([^_]+)_`);
37 const replacement = `<em>$1</em>`;
38 return markdown.replace(pattern, replacement);
2139}
2240
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
41/**
42 * Parses inline formatting (bold and italic)
43 * @param {string} markdown - The markdown text to parse
44 * @param {boolean} inList - Whether we're inside a list
45 * @returns {string} The parsed HTML
46 */
47function parseInlineFormatting(markdown, inList) {
48 // Parse bold first, then italic to avoid conflicts
49 const boldParsed = parseBold(markdown);
50 const italicParsed = parseItalic(boldParsed);
51
52 if (inList) {
53 return italicParsed;
2754 } else {
28 return wrap(parsedText, 'p');
55 return wrap(italicParsed, 'p');
2956 }
3057}
3158
32function parseHeader(markdown, list) {
33 let count = 0;
59/**
60 * Parses header markdown (# ## ### etc.)
61 * @param {string} markdown - The markdown line to parse
62 * @param {boolean} currentlyInList - Whether we're currently in a list
63 * @returns {[string|null, boolean]} [parsed HTML or null if not a header, new list state]
64 */
65function parseHeader(markdown, currentlyInList) {
66 let headerLevel = 0;
67
68 // Count consecutive # characters at the start
3469 for (let i = 0; i < markdown.length; i++) {
3570 if (markdown[i] === '#') {
36 count += 1;
71 headerLevel += 1;
3772 } else {
3873 break;
3974 }
4075 }
41 if (count === 0 || count > 6) {
42 return [null, list];
76
77 // Valid headers are 1-6 # characters
78 if (headerLevel === 0 || headerLevel > 6) {
79 return [null, currentlyInList];
4380 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
81
82 // Extract header content (skip # characters and the following space)
83 const headerContent = markdown.substring(headerLevel + 1);
84 const headerTag = `h${headerLevel}`;
85 const headerHtml = wrap(headerContent, headerTag);
86
87 // If we were in a list, close it before the header
88 if (currentlyInList) {
4789 return [`</ul>${headerHtml}`, false];
4890 } else {
4991 return [headerHtml, false];
5092 }
5193}
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 items (* item)
97 * @param {string} markdown - The markdown line to parse
98 * @param {boolean} currentlyInList - Whether we're currently in a list
99 * @returns {[string|null, boolean]} [parsed HTML or null if not a list item, new list state]
100 */
101function parseListItem(markdown, currentlyInList) {
102 if (markdown.startsWith('* ')) {
103 // Parse the item content with inline formatting but without paragraph wrapper
104 const itemContent = markdown.substring(2);
105 const formattedContent = parseInlineFormatting(itemContent, true);
106 const listItemHtml = wrap(formattedContent, 'li');
107
108 if (currentlyInList) {
109 // Continue existing list
110 return [listItemHtml, true];
58111 } else {
59 return [`<ul>${innerHtml}`, true];
112 // Start new list
113 return [`<ul>${listItemHtml}`, true];
60114 }
61115 }
62 return [null, list];
116
117 return [null, currentlyInList];
63118}
64119
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
120/**
121 * Parses paragraph content
122 * @param {string} markdown - The markdown line to parse
123 * @param {boolean} currentlyInList - Whether we're currently in a list
124 * @returns {[string, boolean]} [parsed HTML, new list state]
125 */
126function parseParagraph(markdown, currentlyInList) {
127 if (!currentlyInList) {
128 // Regular paragraph
129 return [parseInlineFormatting(markdown, false), false];
68130 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
131 // Close list and start paragraph
132 return [`</ul>${parseInlineFormatting(markdown, false)}`, false];
70133 }
71134}
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} markdownLine - The line to parse
139 * @param {boolean} currentlyInList - Whether we're currently in a list
140 * @returns {[string, boolean]} [parsed HTML, new list state]
141 */
142function parseLine(markdownLine, currentlyInList) {
143 // Try to parse as header
144 let [result, newListState] = parseHeader(markdownLine, currentlyInList);
145 if (result !== null) {
146 return [result, newListState];
80147 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
148
149 // Try to parse as list item
150 [result, newListState] = parseListItem(markdownLine, currentlyInList);
151 if (result !== null) {
152 return [result, newListState];
83153 }
84 return [result, inListAfter];
154
155 // Parse as paragraph (default)
156 [result, newListState] = parseParagraph(markdownLine, currentlyInList);
157 return [result, newListState];
85158}
86159
87160/**
88 * @param {string} markdown
89 * @returns {string}
161 * Parses markdown text and converts it to HTML
162 * @param {string} markdown - The markdown text to parse
163 * @returns {string} The resulting HTML
90164 */
91165export function parse(markdown) {
92166 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;
167 let htmlResult = '';
168 let inList = false;
169
170 for (const line of lines) {
171 const [lineHtml, newListState] = parseLine(line, inList);
172 htmlResult += lineHtml;
173 inList = newListState;
99174 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
175
176 // Close any open list at the end
177 if (inList) {
178 htmlResult += '</ul>';
104179 }
180
181 return htmlResult;
105182}
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.