let it go

Finished
1// REFACTORING: Markdown parser
2//
3// This code has been refactored to improve readability and maintainability
4// while preserving all existing functionality. The refactoring focuses on:
5//
6// - Clear separation of concerns between different markdown elements
7// - More functional approach with explicit state management
8// - Consistent use of regex patterns for all parsing operations
9// - Better function organization and naming conventions
10// - Improved code structure for easier understanding and modification
11
12// =============================================================================
13// CONSTANTS AND CONFIGURATION
14// =============================================================================
15
16// Markdown syntax constants
17const MARKDOWN = {
18 HEADER_PATTERN: /^(#{1,6})\s+(.+)$/,
19 LIST_ITEM_PREFIX: '*',
20 BOLD_DELIMITER: '__',
21 ITALIC_DELIMITER: '_',
22 MAX_HEADER_LEVEL: 6
23};
24
25// HTML tag constants
26const HTML_TAGS = {
27 BOLD: 'strong',
28 ITALIC: 'em',
29 PARAGRAPH: 'p',
30 LIST: 'ul',
31 LIST_ITEM: 'li'
32};
33
34// =============================================================================
35// CORE HELPER FUNCTIONS
36// =============================================================================
37
38// Helper function for HTML wrapping
139function wrap(text, tag) {
240 return `<${tag}>${text}</${tag}>`;
341}
442
43// Helper function to check if text starts with a specific HTML tag
544function isTag(text, tag) {
645 return text.startsWith(`<${tag}>`);
746}
847
48// =============================================================================
49// TEXT FORMATTING FUNCTIONS
50// =============================================================================
51
52// Generic text formatting parser using regex patterns
53// Replaces text between delimiters with HTML tags
954function parser(markdown, delimiter, tag) {
1055 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
1156 const replacement = `<${tag}>$1</${tag}>`;
1257 return markdown.replace(pattern, replacement);
1358}
1459
60// Bold text parser using double underscore delimiter
1561function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
62 return parser(markdown, MARKDOWN.BOLD_DELIMITER, HTML_TAGS.BOLD);
1763}
1864
65// Italic text parser using single underscore delimiter
1966function parse_(markdown) {
20 return parser(markdown, '_', 'em');
67 return parser(markdown, MARKDOWN.ITALIC_DELIMITER, HTML_TAGS.ITALIC);
2168}
2269
70// =============================================================================
71// CONTENT PARSING FUNCTIONS
72// =============================================================================
73
74// Text content parser with proper formatting
75// Applies bold and italic formatting, then wraps in paragraph tag if not in a list
2376function parseText(markdown, list) {
2477 const parsedText = parse_(parse__(markdown));
2578 if (list) {
2679 return parsedText;
2780 } else {
28 return wrap(parsedText, 'p');
81 return wrap(parsedText, HTML_TAGS.PARAGRAPH);
2982 }
3083}
3184
85// =============================================================================
86// STRUCTURAL ELEMENT PARSERS
87// =============================================================================
88
89// Header parser with proper level detection and list handling
90// Detects headers using # symbols (1-6 levels) and handles list state transitions
3291function 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) {
92 const headerMatch = markdown.match(MARKDOWN.HEADER_PATTERN);
93 if (!headerMatch) {
4294 return [null, list];
4395 }
44 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
96
97 const [, hashes, content] = headerMatch;
98 const level = hashes.length;
99 const headerTag = `h${level}`;
100 const headerHtml = wrap(content.trim(), headerTag);
101
102 // Close list if we were in one and now have a header
46103 if (list) {
47 return [`</ul>${headerHtml}`, false];
104 return [`</${HTML_TAGS.LIST}>${headerHtml}`, false];
48105 } else {
49106 return [headerHtml, false];
50107 }
51108}
52109
110// List item parser with proper formatting and list state management
111// Detects list items using * symbol and handles list opening/closing
53112function parseLineItem(markdown, list) {
54 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
113 if (markdown.startsWith(MARKDOWN.LIST_ITEM_PREFIX)) {
114 const innerHtml = wrap(parseText(markdown.substring(2), true), HTML_TAGS.LIST_ITEM);
56115 if (list) {
116 // Already in a list, just add the item
57117 return [innerHtml, true];
58118 } else {
59 return [`<ul>${innerHtml}`, true];
119 // Start a new list
120 return [`<${HTML_TAGS.LIST}>${innerHtml}`, true];
60121 }
61122 }
62123 return [null, list];
63124}
64125
126// Paragraph parser with proper formatting and list state management
127// Handles regular text paragraphs and closes lists when transitioning to paragraphs
65128function parseParagraph(markdown, list) {
66129 if (!list) {
130 // Not in a list, just parse as paragraph
67131 return [parseText(markdown, false), false];
68132 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
133 // Close the list and start a paragraph
134 return [`</${HTML_TAGS.LIST}>${parseText(markdown, false)}`, false];
70135 }
71136}
72137
138// =============================================================================
139// MAIN PARSING PIPELINE
140// =============================================================================
141
142// Main line parser that dispatches to appropriate specialized parsers
143// Tries parsers in order: headers -> list items -> paragraphs
144// This provides a clear pipeline for processing different markdown elements
73145function parseLine(markdown, list) {
74146 let [result, inListAfter] = parseHeader(markdown, list);
75147 if (result === null) {
78150 if (result === null) {
79151 [result, inListAfter] = parseParagraph(markdown, list);
80152 }
153
154 // This should never be reached as parseParagraph always returns a result
81155 if (result === null) {
82 throw new Error('Remove this line and implement the function');
156 throw new Error('Unable to parse markdown line');
83157 }
158
84159 return [result, inListAfter];
85160}
86161
87162/**
88 * @param {string} markdown
89 * @returns {string}
163 * Main markdown parser function
164 *
165 * Processes markdown text line by line, maintaining list state across lines.
166 * Uses a pipeline approach: split -> parse lines -> combine results.
167 *
168 * @param {string} markdown - The markdown text to parse
169 * @returns {string} - The resulting HTML
90170 */
91171export function parse(markdown) {
92172 const lines = markdown.split('\n');
93173 let result = '';
94174 let list = false;
175
176 // Process each line, maintaining list state across lines
95177 for (let i = 0; i < lines.length; i++) {
96178 let [lineResult, newList] = parseLine(lines[i], list);
97179 result += lineResult;
98180 list = newList;
99181 }
182
183 // Close any open list at the end
100184 if (list) {
101 return result + '</ul>';
185 return result + `</${HTML_TAGS.LIST}>`;
102186 } else {
103187 return result;
104188 }
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.