A.2.1

Finished
1function wrap(text, tag) {
1// REFACTORING NOTES:
2// 1. Renamed functions to be more descriptive
3// 2. Fixed regex patterns to handle multiple occurrences per line
4// 3. Improved header parsing to handle whitespace properly
5// 4. Enhanced list item parsing to handle variable whitespace
6// 5. Added comprehensive documentation
7// 6. Simplified control flow and reduced complexity
8// 7. Better separation of concerns
9
10/**
11 * Wraps text content in HTML tags
12 * @param {string} text - The text content to wrap
13 * @param {string} tag - The HTML tag name
14 * @returns {string} HTML string with wrapped content
15 */
16function wrapTextInTag(text, tag) {
217 return `<${tag}>${text}</${tag}>`;
318}
419
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
20/**
21 * Applies inline formatting using regex replacement
22 * @param {string} text - The text to format
23 * @param {string} delimiter - The markdown delimiter (e.g., '__', '_')
24 * @param {string} htmlTag - The HTML tag to use (e.g., 'strong', 'em')
25 * @returns {string} Formatted text with HTML tags
26 */
27function applyInlineFormatting(text, delimiter, htmlTag) {
28 // Fixed: Added global flag to handle multiple occurrences per line
29 const pattern = new RegExp(`${delimiter}(.+?)${delimiter}`, 'g');
30 const replacement = `<${htmlTag}>$1</${htmlTag}>`;
31 return text.replace(pattern, replacement);
732}
833
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
12 return markdown.replace(pattern, replacement);
34/**
35 * Converts bold text (surrounded by __) to <strong> tags
36 * @param {string} text - The text to process
37 * @returns {string} Text with bold formatting applied
38 */
39function parseBoldText(text) {
40 return applyInlineFormatting(text, '__', 'strong');
1341}
1442
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
43/**
44 * Converts italic text (surrounded by _) to <em> tags
45 * @param {string} text - The text to process
46 * @returns {string} Text with italic formatting applied
47 */
48function parseItalicText(text) {
49 return applyInlineFormatting(text, '_', 'em');
1750}
1851
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
52/**
53 * Processes inline formatting (bold and italic) for text content
54 * @param {string} text - The text to process
55 * @param {boolean} isListItem - Whether this text is part of a list item
56 * @returns {string} Processed text with inline formatting applied
57 */
58function processInlineFormatting(text, isListItem) {
59 // Apply bold formatting first, then italic
60 const formattedText = parseItalicText(parseBoldText(text));
61
62 // Only wrap in paragraph tags if not a list item
63 return isListItem ? formattedText : wrapTextInTag(formattedText, 'p');
2164}
2265
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
66/**
67 * Parses markdown headers (lines starting with # characters)
68 * @param {string} line - The markdown line to parse
69 * @param {boolean} isInList - Whether we're currently inside a list
70 * @returns {Array} [htmlResult, newInListState] or [null, isInList] if not a header
71 */
72function parseHeader(line, isInList) {
73 // Count consecutive # characters at the start
74 let headerLevel = 0;
75 for (let i = 0; i < line.length && line[i] === '#'; i++) {
76 headerLevel++;
2977 }
78
79 // Valid headers must have 1-6 # characters
80 if (headerLevel === 0 || headerLevel > 6) {
81 return [null, isInList];
82 }
83
84 // Extract content after # characters and trim whitespace
85 // Fixed: Properly handle whitespace after # characters
86 const headerContent = line.substring(headerLevel).trim();
87 const headerTag = `h${headerLevel}`;
88 const headerHtml = wrapTextInTag(headerContent, headerTag);
89
90 // If we were in a list, close it before the header
91 if (isInList) {
92 return [`</ul>${headerHtml}`, false];
93 }
94
95 return [headerHtml, false];
3096}
3197
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 }
98/**
99 * Parses markdown list items (lines starting with *)
100 * @param {string} line - The markdown line to parse
101 * @param {boolean} isInList - Whether we're currently inside a list
102 * @returns {Array} [htmlResult, newInListState] or [null, isInList] if not a list item
103 */
104function parseListItem(line, isInList) {
105 // Check if line starts with a list item marker
106 if (!line.startsWith('*')) {
107 return [null, isInList];
40108 }
41 if (count === 0 || count > 6) {
42 return [null, list];
109
110 // Extract content after * and trim whitespace
111 // Fixed: Handle variable whitespace after asterisk using regex
112 const contentMatch = line.match(/^\*\s+(.*)$/);
113 if (!contentMatch) {
114 return [null, isInList];
43115 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
47 return [`</ul>${headerHtml}`, false];
116
117 const listItemContent = contentMatch[1];
118 const formattedContent = processInlineFormatting(listItemContent, true);
119 const listItemHtml = wrapTextInTag(formattedContent, 'li');
120
121 // If we weren't in a list, start one; otherwise continue the existing list
122 if (isInList) {
123 return [listItemHtml, true];
48124 } else {
49 return [headerHtml, false];
125 return [`<ul>${listItemHtml}`, true];
50126 }
51127}
52128
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];
129/**
130 * Parses regular paragraph text (default case when not header or list item)
131 * @param {string} line - The markdown line to parse
132 * @param {boolean} isInList - Whether we're currently inside a list
133 * @returns {Array} [htmlResult, newInListState]
134 */
135function parseParagraph(line, isInList) {
136 // Handle empty lines - don't wrap them in paragraph tags
137 if (line.trim() === '') {
138 // If we were in a list, close it
139 if (isInList) {
140 return ['</ul>', false];
60141 }
142 return ['', false];
61143 }
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];
144
145 const paragraphHtml = processInlineFormatting(line, false);
146
147 // If we were in a list, close it before the paragraph
148 if (isInList) {
149 return [`</ul>${paragraphHtml}`, false];
70150 }
151
152 return [paragraphHtml, false];
71153}
72154
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
77 }
155/**
156 * Determines the type of markdown line and routes it to the appropriate parser
157 * @param {string} line - The markdown line to parse
158 * @param {boolean} isInList - Whether we're currently inside a list
159 * @returns {Array} [htmlResult, newInListState]
160 */
161function parseLine(line, isInList) {
162 // Try to parse as header first
163 let [result, newInListState] = parseHeader(line, isInList);
164
165 // If not a header, try to parse as list item
78166 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
167 [result, newInListState] = parseListItem(line, isInList);
80168 }
169
170 // If not a header or list item, treat as paragraph
81171 if (result === null) {
82 throw new Error('Remove this line and implement the function');
172 [result, newInListState] = parseParagraph(line, isInList);
83173 }
84 return [result, inListAfter];
174
175 // At this point, result should never be null as parseParagraph handles all cases
176 return [result, newInListState];
85177}
86178
87179/**
88 * @param {string} markdown
89 * @returns {string}
180 * Parses a markdown string and converts it to HTML
181 * @param {string} markdown - The markdown text to parse
182 * @returns {string} The resulting HTML
90183 */
91184export function parse(markdown) {
92185 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;
186 let htmlResult = '';
187 let isInList = false;
188
189 // Process each line of the markdown text
190 for (const line of lines) {
191 const [lineHtml, newListState] = parseLine(line, isInList);
192 htmlResult += lineHtml;
193 isInList = newListState;
99194 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
195
196 // Close any open list tag at the end
197 if (isInList) {
198 htmlResult += '</ul>';
104199 }
200
201 return htmlResult;
105202}
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.