couple

Finished
1// REFACTORING NOTES:
2// 1. Fixed regex bug: Changed greedy (.+) to non-greedy (.+?) to handle mixed bold/italic correctly
3// 2. Improved function naming: parse__ -> parseBold, parse_ -> parseItalic
4// 3. Better separation of concerns: Text formatting separated from HTML structure
5// 4. Removed unused isTag function
6// 5. More descriptive variable names
7
8/**
9 * Wraps text in HTML tags
10 * @param {string} text - The text to wrap
11 * @param {string} tag - The HTML tag name
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}`);
18/**
19 * Parses markdown text with delimiters and wraps in HTML tags
20 * @param {string} markdown - The markdown text to parse
21 * @param {string} delimiter - The delimiter to search for
22 * @param {string} tag - The HTML tag to use
23 * @returns {string} The parsed HTML
24 */
25function parseWithDelimiter(markdown, delimiter, tag) {
26 // Fixed: Use non-greedy matching (.+?) instead of greedy (.+)
27 // This prevents incorrect matching when delimiters are nested
28 const pattern = new RegExp(`${delimiter}(.+?)${delimiter}`, 'g');
1129 const replacement = `<${tag}>$1</${tag}>`;
1230 return markdown.replace(pattern, replacement);
1331}
1432
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
33/**
34 * Parses bold text (using __ delimiter)
35 * @param {string} markdown - The markdown text to parse
36 * @returns {string} The parsed HTML with bold tags
37 */
38function parseBold(markdown) {
39 return parseWithDelimiter(markdown, '__', 'strong');
1740}
1841
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
42/**
43 * Parses italic text (using _ delimiter)
44 * @param {string} markdown - The markdown text to parse
45 * @returns {string} The parsed HTML with italic tags
46 */
47function parseItalic(markdown) {
48 return parseWithDelimiter(markdown, '_', 'em');
2149}
2250
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
51/**
52 * Parses inline formatting (bold and italic) in markdown text
53 * @param {string} markdown - The markdown text to parse
54 * @returns {string} The text with inline formatting applied
55 */
56function parseInlineFormatting(markdown) {
57 // Parse bold first, then italic to handle nested cases correctly
58 return parseItalic(parseBold(markdown));
59}
60
61/**
62 * Parses text content and optionally wraps in paragraph tags
63 * @param {string} markdown - The markdown text to parse
64 * @param {boolean} isListItem - Whether this text is part of a list item
65 * @returns {string} The parsed text, wrapped in paragraph tags if not a list item
66 */
67function parseText(markdown, isListItem = false) {
68 const parsedText = parseInlineFormatting(markdown);
69 return isListItem ? parsedText : wrap(parsedText, 'p');
70}
71
72/**
73 * Parses header lines (1-6 levels using # symbols)
74 * @param {string} markdown - The markdown line to parse
75 * @param {boolean} isInList - Whether we're currently inside a list
76 * @returns {Array|null} [parsedHtml, isInListAfter] or null if not a header
77 */
78function parseHeader(markdown, isInList) {
79 const headerLevel = countHeaderLevel(markdown);
80
81 // Return null if not a valid header (0 or >6 # symbols)
82 if (headerLevel === 0) {
83 return [null, isInList];
2984 }
85
86 const headerContent = markdown.substring(headerLevel + 1).trim();
87 const headerTag = `h${headerLevel}`;
88 const headerHtml = wrap(headerContent, headerTag);
89
90 // Close any open list before starting a header
91 const resultHtml = isInList ? `</ul>${headerHtml}` : headerHtml;
92
93 return [resultHtml, false]; // Headers always end list context
3094}
3195
32function parseHeader(markdown, list) {
96/**
97 * Counts the number of consecutive # symbols at the start of a line
98 * @param {string} markdown - The markdown line to analyze
99 * @returns {number} The header level (0-6)
100 */
101function countHeaderLevel(markdown) {
33102 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];
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];
103 for (let i = 0; i < markdown.length && markdown[i] === '#'; i++) {
104 count++;
50105 }
106 return Math.min(count, 6); // Cap at 6 as per HTML spec
51107}
52108
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 }
109/**
110 * Parses list items (lines starting with *)
111 * @param {string} markdown - The markdown line to parse
112 * @param {boolean} isInList - Whether we're currently inside a list
113 * @returns {Array|null} [parsedHtml, isInListAfter] or null if not a list item
114 */
115function parseListItem(markdown, isInList) {
116 if (!markdown.startsWith('*')) {
117 return [null, isInList];
61118 }
62 return [null, list];
119
120 // Remove the '* ' prefix and parse the content
121 const itemContent = markdown.substring(2).trim();
122 const parsedContent = parseText(itemContent, true); // true = is list item
123 const listItemHtml = wrap(parsedContent, 'li');
124
125 // Start new list if not already in one
126 const resultHtml = isInList ? listItemHtml : `<ul>${listItemHtml}`;
127
128 return [resultHtml, true]; // List items keep us in list context
63129}
64130
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
70 }
131/**
132 * Parses regular paragraph text
133 * @param {string} markdown - The markdown line to parse
134 * @param {boolean} isInList - Whether we're currently inside a list
135 * @returns {Array} [parsedHtml, isInListAfter]
136 */
137function parseParagraph(markdown, isInList) {
138 const parsedText = parseText(markdown, false); // false = not a list item
139
140 // Close any open list before starting a paragraph
141 const resultHtml = isInList ? `</ul>${parsedText}` : parsedText;
142
143 return [resultHtml, false]; // Paragraphs always end list context
71144}
72145
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);
80 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
146/**
147 * Parses a single line of markdown by trying different parsers in order
148 * @param {string} markdown - The markdown line to parse
149 * @param {boolean} isInList - Whether we're currently inside a list
150 * @returns {Array} [parsedHtml, isInListAfter]
151 */
152function parseLine(markdown, isInList) {
153 // Try parsers in order of precedence: headers -> list items -> paragraphs
154 const parsers = [
155 parseHeader,
156 parseListItem,
157 parseParagraph
158 ];
159
160 for (const parser of parsers) {
161 const [result, newInListState] = parser(markdown, isInList);
162 if (result !== null) {
163 return [result, newInListState];
164 }
83165 }
84 return [result, inListAfter];
166
167 // This should never happen as parseParagraph always returns a result
168 throw new Error('Unable to parse markdown line');
85169}
86170
87171/**
Test NameStatus
with h7 header level
Fail
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
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.