Alone

Finished
1function wrap(text, tag) {
2 return `<${tag}>${text}</${tag}>`;
3}
4
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);
13}
14
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
1/**
2 * A Markdown parser that converts Markdown syntax to HTML
3 * Refactored for better readability and maintainability
4 */
5class MarkdownParser {
6 constructor() {
7 this.inList = false;
8 }
189
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
21}
10 /**
11 * Parses inline formatting like bold (__text__) and italic (_text_)
12 * @param {string} text - The text to parse
13 * @returns {string} - The text with HTML formatting applied
14 */
15 parseInlineFormatting(text) {
16 // Parse bold formatting (__text__ -> <strong>text</strong>)
17 let result = text.replace(/__(.+)__/g, '<strong>$1</strong>');
18 // Parse italic formatting (_text_ -> <em>text</em>)
19 result = result.replace(/_(.+)_/g, '<em>$1</em>');
20 return result;
21 }
2222
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
23 /**
24 * Wraps text with HTML tags
25 * @param {string} text - The text to wrap
26 * @param {string} tag - The HTML tag to use
27 * @returns {string} - The wrapped text
28 */
29 wrapText(text, tag) {
30 return `<${tag}>${text}</${tag}>`;
2931 }
30}
3132
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;
33 /**
34 * Parses a header line (# Header -> <h1>Header</h1>)
35 * @param {string} line - The line to parse
36 * @returns {string|null} - The parsed header HTML or null if not a header
37 */
38 parseHeader(line) {
39 const headerMatch = line.match(/^(#{1,6})\s(.*)/);
40 if (!headerMatch) return null;
41
42 const level = headerMatch[1].length;
43 const text = headerMatch[2];
44 const formattedText = this.parseInlineFormatting(text);
45
46 // Close list if we're in one
47 let result = '';
48 if (this.inList) {
49 result += '</ul>';
50 this.inList = false;
3951 }
52
53 return result + this.wrapText(formattedText, `h${level}`);
4054 }
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}
5255
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];
56 /**
57 * Parses a list item (* item -> <li>item</li>)
58 * @param {string} line - The line to parse
59 * @returns {string|null} - The parsed list item HTML or null if not a list item
60 */
61 parseListItem(line) {
62 if (!line.startsWith('* ')) return null;
63
64 const text = line.substring(2);
65 const formattedText = this.parseInlineFormatting(text);
66
67 // Open list if we're not already in one
68 let result = '';
69 if (!this.inList) {
70 result += '<ul>';
71 this.inList = true;
6072 }
73
74 return result + this.wrapText(formattedText, 'li');
6175 }
62 return [null, list];
63}
6476
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
77 /**
78 * Parses a paragraph (regular text -> <p>text</p>)
79 * @param {string} line - The line to parse
80 * @returns {string} - The parsed paragraph HTML
81 */
82 parseParagraph(line) {
83 const formattedText = this.parseInlineFormatting(line);
84
85 // Close list if we're in one
86 let result = '';
87 if (this.inList) {
88 result += '</ul>';
89 this.inList = false;
90 }
91
92 return result + this.wrapText(formattedText, 'p');
7093 }
71}
7294
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);
95 /**
96 * Parses a single line of Markdown
97 * @param {string} line - The line to parse
98 * @returns {string} - The parsed HTML
99 */
100 parseLine(line) {
101 // Try to parse as header
102 const headerResult = this.parseHeader(line);
103 if (headerResult !== null) return headerResult;
104
105 // Try to parse as list item
106 const listItemResult = this.parseListItem(line);
107 if (listItemResult !== null) return listItemResult;
108
109 // Parse as paragraph (default)
110 return this.parseParagraph(line);
80111 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
112
113 /**
114 * Parses a complete Markdown text
115 * @param {string} markdown - The Markdown text to parse
116 * @returns {string} - The parsed HTML
117 */
118 parse(markdown) {
119 // Reset state
120 this.inList = false;
121
122 // Split into lines and parse each line
123 // Filter out trailing empty lines to match original behavior
124 // But keep at least one line if the input is not empty
125 let lines = markdown.split('\n');
126 if (lines.length > 1) {
127 lines = lines.filter((line, index, arr) =>
128 !(line === '' && index === arr.length - 1)
129 );
130 }
131 let result = '';
132
133 for (const line of lines) {
134 result += this.parseLine(line);
135 }
136
137 // Close any open list at the end
138 if (this.inList) {
139 result += '</ul>';
140 }
141
142 return result;
83143 }
84 return [result, inListAfter];
85144}
86145
87146/**
88 * @param {string} markdown
89 * @returns {string}
147 * Parses Markdown text and converts it to HTML
148 * @param {string} markdown - The Markdown text to parse
149 * @returns {string} - The resulting HTML
90150 */
91151export 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 }
152 const parser = new MarkdownParser();
153 return parser.parse(markdown);
105154}
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.