@takazudo/mdx-formatter 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +128 -0
  5. package/dist/html-block-formatter.d.ts +46 -0
  6. package/dist/html-block-formatter.js +370 -0
  7. package/dist/hybrid-formatter.d.ts +59 -0
  8. package/dist/hybrid-formatter.js +977 -0
  9. package/dist/indent-detector.d.ts +62 -0
  10. package/dist/indent-detector.js +358 -0
  11. package/dist/index.d.ts +28 -0
  12. package/dist/index.js +57 -0
  13. package/dist/load-config.d.ts +13 -0
  14. package/dist/load-config.js +71 -0
  15. package/dist/plugins/docusaurus-admonitions.d.ts +5 -0
  16. package/dist/plugins/docusaurus-admonitions.js +42 -0
  17. package/dist/plugins/fix-autolink-output.d.ts +4 -0
  18. package/dist/plugins/fix-autolink-output.js +24 -0
  19. package/dist/plugins/fix-formatting-issues.d.ts +4 -0
  20. package/dist/plugins/fix-formatting-issues.js +42 -0
  21. package/dist/plugins/fix-paragraph-spacing.d.ts +5 -0
  22. package/dist/plugins/fix-paragraph-spacing.js +96 -0
  23. package/dist/plugins/html-definition-list.d.ts +5 -0
  24. package/dist/plugins/html-definition-list.js +64 -0
  25. package/dist/plugins/japanese-text.d.ts +5 -0
  26. package/dist/plugins/japanese-text.js +79 -0
  27. package/dist/plugins/normalize-lists.d.ts +5 -0
  28. package/dist/plugins/normalize-lists.js +58 -0
  29. package/dist/plugins/preprocess-japanese.d.ts +7 -0
  30. package/dist/plugins/preprocess-japanese.js +15 -0
  31. package/dist/plugins/preserve-image-alt.d.ts +8 -0
  32. package/dist/plugins/preserve-image-alt.js +19 -0
  33. package/dist/plugins/preserve-jsx.d.ts +6 -0
  34. package/dist/plugins/preserve-jsx.js +48 -0
  35. package/dist/settings.d.ts +7 -0
  36. package/dist/settings.js +91 -0
  37. package/dist/specific-formatter.d.ts +30 -0
  38. package/dist/specific-formatter.js +469 -0
  39. package/dist/types.d.ts +226 -0
  40. package/dist/types.js +4 -0
  41. package/dist/utils.d.ts +12 -0
  42. package/dist/utils.js +47 -0
  43. package/format-stdin.js +36 -0
  44. package/package.json +107 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Post-processing fixes for common formatting issues
3
+ */
4
+ export function fixFormattingIssues(content) {
5
+ let fixed = content;
6
+ // Fix 1: Restore spaces between bold elements and operators
7
+ // When bold text is followed by operators like +, =, etc., ensure there's a space
8
+ // This must be done BEFORE fixing the internal spaces to preserve the pattern
9
+ // Use negative lookahead to avoid matching operators that already have a space
10
+ fixed = fixed.replace(/(\*\*[^*]+\*\*)([+\-=])/g, '$1 $2');
11
+ // Fix 2: Restore spaces before bold elements after operators
12
+ // Ensure there's a space after operators before bold text
13
+ fixed = fixed.replace(/([+\-=])(\*\*[^*]+\*\*)/g, '$1 $2');
14
+ // Fix 3: Restore spaces between word characters and bold elements
15
+ fixed = fixed.replace(/([a-zA-Z])\s*(\*\*[^*]+\*\*)/g, '$1 $2');
16
+ // Fix 4: Remove extra spaces inside bold asterisks (do this LAST)
17
+ // Sometimes remark adds a space before the closing ** for short words
18
+ // Pattern: **word ** -> **word**
19
+ fixed = fixed.replace(/\*\*([^*]+)\s+\*\*/g, '**$1**');
20
+ // Fix 5: Remove extra trailing spaces inside HTML tags (2+ spaces only)
21
+ // Pattern: <tag>content </tag> -> <tag>content </tag> (preserves single space)
22
+ fixed = fixed.replace(/(<[^/>]+>)([^<]*)\s{2,}(<\/[^>]+>)/g, '$1$2 $3');
23
+ // Fix 6: Collapse multiple spaces before self-closing HTML tags to single space
24
+ // Pattern: text <br/> -> text <br/> (preserves meaningful single space)
25
+ fixed = fixed.replace(/\s{2,}(<[^>]+\/>)/g, ' $1');
26
+ // Fix 7: Fix HTML entity encoding for Japanese characters
27
+ // Specifically fix the common case of を being encoded as &#x3092;
28
+ fixed = fixed.replace(/&#x3092;/g, 'を');
29
+ // Fix other common Japanese character encodings if they appear
30
+ fixed = fixed.replace(/&#x([0-9a-fA-F]{4});/g, (match, hex) => {
31
+ const code = parseInt(hex, 16);
32
+ // Only decode Japanese characters (Hiragana, Katakana, Kanji ranges)
33
+ if ((code >= 0x3040 && code <= 0x309f) || // Hiragana
34
+ (code >= 0x30a0 && code <= 0x30ff) || // Katakana
35
+ (code >= 0x4e00 && code <= 0x9faf)) {
36
+ // CJK Unified Ideographs
37
+ return String.fromCharCode(code);
38
+ }
39
+ return match;
40
+ });
41
+ return fixed;
42
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Plugin to fix paragraph and JSX spacing issues
3
+ * This runs as a post-processing step after stringify
4
+ */
5
+ export declare function fixParagraphSpacing(content: string): string;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Plugin to fix paragraph and JSX spacing issues
3
+ * This runs as a post-processing step after stringify
4
+ */
5
+ export function fixParagraphSpacing(content) {
6
+ let fixed = content;
7
+ // Fix 0: Split collapsed JSX components (most critical fix)
8
+ // Pattern: /><Component should be />\n\n<Component
9
+ // Use a loop to handle all occurrences (in case of multiple collapsed components)
10
+ let previousFixed = '';
11
+ while (previousFixed !== fixed) {
12
+ previousFixed = fixed;
13
+ fixed = fixed.replace(/(\/>)(<[A-Z][^>]*>)/g, '$1\n\n$2');
14
+ }
15
+ // Fix 1: Ensure blank lines between paragraphs
16
+ // Split content by lines and process
17
+ const lines = fixed.split('\n');
18
+ const processedLines = [];
19
+ for (let i = 0; i < lines.length; i++) {
20
+ const line = lines[i];
21
+ const nextLine = lines[i + 1];
22
+ processedLines.push(line);
23
+ // Check if we need to add a blank line
24
+ if (line && nextLine && i < lines.length - 1) {
25
+ const currentEndsWithText = /[。!?\.!?)\]】a-zA-Z0-9]$/.test(line.trim());
26
+ const nextStartsWithJSX = /^<[A-Z]/.test(nextLine.trim());
27
+ // Add blank line between text and JSX components
28
+ if (currentEndsWithText && nextStartsWithJSX) {
29
+ processedLines.push('');
30
+ }
31
+ // Add blank line between heading and content
32
+ else if (/^#{1,6} /.test(line.trim()) && nextLine.trim() && !lines[i - 1]?.trim()) {
33
+ processedLines.push('');
34
+ }
35
+ // Add blank line between import/export and other content
36
+ else if ((line.trim().startsWith('import ') || line.trim().startsWith('export ')) &&
37
+ nextLine.trim() &&
38
+ !nextLine.trim().startsWith('import ') &&
39
+ !nextLine.trim().startsWith('export ')) {
40
+ processedLines.push('');
41
+ }
42
+ }
43
+ }
44
+ fixed = processedLines.join('\n');
45
+ // Fix 2: Ensure JSX components are not merged with text on same line
46
+ // Pattern: text<Component should be text\n\n<Component
47
+ fixed = fixed.replace(/([。!?\.!?)\]】a-zA-Z0-9])(<[A-Z][^>]*>)/g, '$1\n\n$2');
48
+ // Fix 3: Ensure import statements are not merged with JSX
49
+ fixed = fixed.replace(/(import [^;]+;)(<[A-Z])/g, '$1\n\n$2');
50
+ // Fix 4: Ensure headings are not merged with content
51
+ fixed = fixed.replace(/(^#{1,6} [^\n]+)(<[A-Z])/gm, '$1\n\n$2');
52
+ // Fix 5: Split merged paragraphs (Japanese text specific)
53
+ // Pattern: 。text should be 。\ntext when it's a new sentence
54
+ fixed = fixed.replace(/([。!?])([ぁ-んァ-ヶー一-龠]+)/g, (match, punct, text) => {
55
+ // Check if the text after punctuation looks like a new sentence
56
+ // (starts with a capital-like character or common sentence starters)
57
+ if (/^[こそたなはまやらわがざだばぱ]/.test(text)) {
58
+ return `${punct}\n${text}`;
59
+ }
60
+ return match;
61
+ });
62
+ // Fix 6: Preserve space after question mark in Japanese
63
+ fixed = fixed.replace(/([?!])([^ \n])/g, '$1 $2');
64
+ // Fix 7: Clean up excessive blank lines (more than 2 consecutive)
65
+ fixed = fixed.replace(/\n{4,}/g, '\n\n\n');
66
+ // Fix 8: Fix JSX indentation issues
67
+ // When we have closing tags that lost one space of indentation
68
+ fixed = fixed.replace(/\n( +)<\/([A-Z][^>]+)>/g, (match, spaces, tag) => {
69
+ // Check if the opening tag had more indentation
70
+ const openingPattern = new RegExp(`\\n(\\s+)<${tag}[^>]*>`, 'g');
71
+ const openingMatch = fixed.match(openingPattern);
72
+ if (openingMatch && openingMatch[0]) {
73
+ const openingSpacesMatch = openingMatch[0].match(/\n(\s+)</);
74
+ if (openingSpacesMatch) {
75
+ const openingSpaces = openingSpacesMatch[1];
76
+ if (openingSpaces.length === spaces.length + 1) {
77
+ // The opening tag has one more space, so add it to closing tag
78
+ return '\n' + spaces + ' </' + tag + '>';
79
+ }
80
+ }
81
+ }
82
+ return match;
83
+ });
84
+ // Fix 9: Fix Outro component spacing specifically
85
+ // Ensure content inside Outro has proper spacing
86
+ fixed = fixed.replace(/<Outro>([\s\S]*?)<\/Outro>/g, (match, content) => {
87
+ // Check if there's content
88
+ const trimmedContent = content.trim();
89
+ if (trimmedContent) {
90
+ // Ensure double newlines before and after content
91
+ return '<Outro>\n\n' + trimmedContent + '\n\n</Outro>';
92
+ }
93
+ return match;
94
+ });
95
+ return fixed;
96
+ }
@@ -0,0 +1,5 @@
1
+ import type { Root } from 'mdast';
2
+ /**
3
+ * Plugin to convert HTML definition lists to markdown
4
+ */
5
+ export declare function htmlDefinitionListPlugin(): (tree: Root) => void;
@@ -0,0 +1,64 @@
1
+ import { visit } from 'unist-util-visit';
2
+ /**
3
+ * Plugin to convert HTML definition lists to markdown
4
+ */
5
+ export function htmlDefinitionListPlugin() {
6
+ return (tree) => {
7
+ // In MDX, raw HTML can appear as either 'html' or 'mdxFlowExpression' nodes
8
+ // We need to handle both cases
9
+ visit(tree, (node, index, parent) => {
10
+ if (node.type !== 'html' && node.type !== 'raw')
11
+ return;
12
+ const htmlNode = node;
13
+ const parentNode = parent;
14
+ // For 'raw' nodes (MDX), the content is in node.value
15
+ // For 'html' nodes (regular markdown), it's also in node.value
16
+ if (!htmlNode.value)
17
+ return;
18
+ // Check if this is a definition list
19
+ const dlMatch = htmlNode.value.match(/^<dl[^>]*>([\s\S]*?)<\/dl>$/);
20
+ if (!dlMatch)
21
+ return;
22
+ const content = dlMatch[1];
23
+ const items = [];
24
+ // Parse dt/dd pairs
25
+ const regex = /<dt[^>]*>([\s\S]*?)<\/dt>\s*<dd[^>]*>([\s\S]*?)<\/dd>/g;
26
+ let match;
27
+ while ((match = regex.exec(content)) !== null) {
28
+ const term = cleanHtml(match[1].trim());
29
+ const definition = cleanHtml(match[2].trim());
30
+ // Create markdown definition list nodes
31
+ items.push({
32
+ type: 'paragraph',
33
+ children: [
34
+ {
35
+ type: 'strong',
36
+ children: [{ type: 'text', value: term }],
37
+ },
38
+ ],
39
+ });
40
+ items.push({
41
+ type: 'paragraph',
42
+ children: [{ type: 'text', value: ': ' + definition }],
43
+ });
44
+ }
45
+ // Replace the HTML node with markdown nodes
46
+ if (items.length > 0 && parentNode && typeof index === 'number') {
47
+ parentNode.children.splice(index, 1, ...items);
48
+ }
49
+ });
50
+ };
51
+ }
52
+ /**
53
+ * Clean HTML tags from text content
54
+ */
55
+ function cleanHtml(html) {
56
+ return html
57
+ .replace(/<code[^>]*>(.*?)<\/code>/g, '`$1`')
58
+ .replace(/<strong[^>]*>(.*?)<\/strong>/g, '**$1**')
59
+ .replace(/<b[^>]*>(.*?)<\/b>/g, '**$1**')
60
+ .replace(/<em[^>]*>(.*?)<\/em>/g, '*$1*')
61
+ .replace(/<i[^>]*>(.*?)<\/i>/g, '*$1*')
62
+ .replace(/<[^>]+>/g, '')
63
+ .trim();
64
+ }
@@ -0,0 +1,5 @@
1
+ import type { Root } from 'mdast';
2
+ /**
3
+ * Plugin to handle Japanese text formatting rules
4
+ */
5
+ export declare function japaneseTextPlugin(): (tree: Root) => void;
@@ -0,0 +1,79 @@
1
+ import { visit } from 'unist-util-visit';
2
+ /**
3
+ * Plugin to handle Japanese text formatting rules
4
+ */
5
+ export function japaneseTextPlugin() {
6
+ return (tree) => {
7
+ // Note: Japanese URLs are now pre-processed in the main format function
8
+ // to prevent GFM from incorrectly parsing them
9
+ // Clean up Japanese text
10
+ visit(tree, 'text', (node, index, parent) => {
11
+ const textNode = node;
12
+ const parentNode = parent;
13
+ if (!textNode.value)
14
+ return;
15
+ // Check if this text node is adjacent to inline code or strong/emphasis
16
+ // If so, preserve spaces around it
17
+ const isBeforeInlineCode = parentNode &&
18
+ parentNode.children &&
19
+ typeof index === 'number' &&
20
+ index < parentNode.children.length - 1 &&
21
+ parentNode.children[index + 1].type === 'inlineCode';
22
+ const isAfterInlineCode = parentNode &&
23
+ parentNode.children &&
24
+ typeof index === 'number' &&
25
+ index > 0 &&
26
+ parentNode.children[index - 1].type === 'inlineCode';
27
+ // Check if adjacent to strong or emphasis elements
28
+ const isBeforeStrong = parentNode &&
29
+ parentNode.children &&
30
+ typeof index === 'number' &&
31
+ index < parentNode.children.length - 1 &&
32
+ (parentNode.children[index + 1].type === 'strong' ||
33
+ parentNode.children[index + 1].type === 'emphasis');
34
+ const isAfterStrong = parentNode &&
35
+ parentNode.children &&
36
+ typeof index === 'number' &&
37
+ index > 0 &&
38
+ (parentNode.children[index - 1].type === 'strong' ||
39
+ parentNode.children[index - 1].type === 'emphasis');
40
+ // Preserve Japanese punctuation and spacing
41
+ // Don't add extra spaces around Japanese punctuation
42
+ textNode.value = textNode.value
43
+ .replace(/\s+([、。!?])/g, '$1') // Remove spaces before Japanese punctuation
44
+ .replace(/([、。!?])\s+/g, '$1'); // Remove spaces after Japanese punctuation
45
+ // Only trim trailing spaces if:
46
+ // 1. Not adjacent to code or strong/emphasis elements
47
+ // 2. The text node doesn't contain operators that need spacing
48
+ // This preserves spaces between bold elements like "**VCA** + **Envelope**"
49
+ const hasOperators = /[+\-=*/<>]/.test(textNode.value);
50
+ if (!isBeforeInlineCode &&
51
+ !isAfterInlineCode &&
52
+ !isBeforeStrong &&
53
+ !isAfterStrong &&
54
+ !hasOperators) {
55
+ // Only trim if the entire value is whitespace or ends with multiple spaces
56
+ if (textNode.value.trim() === '') {
57
+ textNode.value = '';
58
+ }
59
+ else if (/\s{2,}$/.test(textNode.value)) {
60
+ // Only trim if there are 2+ trailing spaces
61
+ textNode.value = textNode.value.replace(/\s+$/g, ' ');
62
+ }
63
+ }
64
+ });
65
+ // Handle spacing around Japanese headings
66
+ visit(tree, 'heading', (node) => {
67
+ const heading = node;
68
+ if (heading.children && heading.children[0] && heading.children[0].type === 'text') {
69
+ const textChild = heading.children[0];
70
+ const text = textChild.value;
71
+ // Check if the heading contains Japanese characters
72
+ if (/[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uffef\u4e00-\u9faf]/.test(text)) {
73
+ // Ensure proper formatting for Japanese headings
74
+ textChild.value = text.trim();
75
+ }
76
+ }
77
+ });
78
+ };
79
+ }
@@ -0,0 +1,5 @@
1
+ import type { Root } from 'mdast';
2
+ /**
3
+ * Plugin to normalize list markers and merge adjacent lists
4
+ */
5
+ export declare function normalizeListsPlugin(): (tree: Root) => void;
@@ -0,0 +1,58 @@
1
+ import { visit } from 'unist-util-visit';
2
+ /**
3
+ * Plugin to normalize list markers and merge adjacent lists
4
+ */
5
+ export function normalizeListsPlugin() {
6
+ return (tree) => {
7
+ // First, merge consecutive lists of the same type
8
+ visit(tree, (node) => {
9
+ const parent = node;
10
+ // Check if this node has children array
11
+ if (!parent.children || !Array.isArray(parent.children))
12
+ return;
13
+ const newChildren = [];
14
+ let i = 0;
15
+ while (i < parent.children.length) {
16
+ const child = parent.children[i];
17
+ // Check if this is a list and the next child is also a list of the same type
18
+ if (child.type === 'list' && i + 1 < parent.children.length) {
19
+ const nextChild = parent.children[i + 1];
20
+ if (nextChild.type === 'list' &&
21
+ child.ordered === nextChild.ordered &&
22
+ child.start === nextChild.start) {
23
+ // Merge the lists
24
+ child.children = [...child.children, ...nextChild.children];
25
+ i++; // Skip the next child since we merged it
26
+ }
27
+ }
28
+ newChildren.push(child);
29
+ i++;
30
+ }
31
+ parent.children = newChildren;
32
+ });
33
+ // Normalize all unordered list items to use - as marker
34
+ visit(tree, 'listItem', (node, _index, parent) => {
35
+ const listItem = node;
36
+ const parentList = parent;
37
+ if (parentList && parentList.ordered === false) {
38
+ // This is an unordered list item
39
+ // The marker is controlled by stringify options, but we ensure consistency
40
+ if (listItem.data) {
41
+ delete listItem.data.marker; // Remove any specific marker data
42
+ }
43
+ }
44
+ });
45
+ // Ensure list structure is correct
46
+ visit(tree, 'list', (node) => {
47
+ const list = node;
48
+ // Ensure ordered property is set correctly
49
+ if (list.ordered === undefined) {
50
+ list.ordered = false; // Default to unordered
51
+ }
52
+ // Mark as tight list if items are not spread
53
+ if (list.children && list.children.every((item) => !item.spread)) {
54
+ list.spread = false;
55
+ }
56
+ });
57
+ };
58
+ }
@@ -0,0 +1,7 @@
1
+ import type { Root } from 'mdast';
2
+ import type { VFile } from 'vfile';
3
+ /**
4
+ * Pre-processing plugin to normalize Japanese text before parsing
5
+ * This runs on the raw text before the markdown parser
6
+ */
7
+ export declare function preprocessJapanesePlugin(): (_tree: Root, file: VFile) => void;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Pre-processing plugin to normalize Japanese text before parsing
3
+ * This runs on the raw text before the markdown parser
4
+ */
5
+ export function preprocessJapanesePlugin() {
6
+ return function (_tree, file) {
7
+ // Work on the raw content before parsing
8
+ const content = String(file);
9
+ // Convert Japanese parentheses with URLs to standard markdown links
10
+ const urlInParensPattern = /([^()]+)((https?:\/\/[^)]+))/g;
11
+ const processed = content.replace(urlInParensPattern, '[$1]($2)');
12
+ // Update the file content
13
+ file.value = processed;
14
+ };
15
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Pre-process content to preserve image alt text from directive parsing
3
+ */
4
+ export declare function protectImageAltText(content: string): string;
5
+ /**
6
+ * Post-process content to restore image alt text
7
+ */
8
+ export declare function restoreImageAltText(content: string): string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Pre-process content to preserve image alt text from directive parsing
3
+ */
4
+ export function protectImageAltText(content) {
5
+ // Replace colons in image alt text with a placeholder that won't be parsed as a directive
6
+ // Match ![...] patterns and protect ALL colons within them
7
+ return content.replace(/!\[([^\]]*)\]/g, (_match, altText) => {
8
+ // Replace all colons in the alt text with a placeholder
9
+ const protectedAlt = altText.replace(/:/g, '___COLON___');
10
+ return `![${protectedAlt}]`;
11
+ });
12
+ }
13
+ /**
14
+ * Post-process content to restore image alt text
15
+ */
16
+ export function restoreImageAltText(content) {
17
+ // Restore the colons in image alt text - handle both escaped and unescaped versions
18
+ return content.replace(/___COLON___/g, ':').replace(/\\_\\_\\_COLON\\_\\_\\_/g, ':'); // In case underscores got escaped
19
+ }
@@ -0,0 +1,6 @@
1
+ import type { Root } from 'mdast';
2
+ import type { VFile } from 'vfile';
3
+ /**
4
+ * Plugin to preserve JSX/MDX component formatting and indentation
5
+ */
6
+ export declare function preserveJsxPlugin(): (tree: Root, file: VFile) => void;
@@ -0,0 +1,48 @@
1
+ import { visit } from 'unist-util-visit';
2
+ /**
3
+ * Plugin to preserve JSX/MDX component formatting and indentation
4
+ */
5
+ export function preserveJsxPlugin() {
6
+ return (tree, file) => {
7
+ const originalContent = String(file);
8
+ const lines = originalContent.split('\n');
9
+ visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], (node) => {
10
+ const jsxNode = node;
11
+ // Store the original JSX content to preserve formatting
12
+ if (jsxNode.position && jsxNode.position.start && jsxNode.position.end) {
13
+ const startLine = jsxNode.position.start.line - 1;
14
+ const startCol = jsxNode.position.start.column - 1;
15
+ const endLine = jsxNode.position.end.line - 1;
16
+ const endCol = jsxNode.position.end.column;
17
+ // Extract the original JSX content preserving indentation
18
+ let originalJsx = '';
19
+ if (startLine === endLine) {
20
+ // Single line JSX
21
+ originalJsx = lines[startLine].substring(startCol, endCol);
22
+ }
23
+ else {
24
+ // Multi-line JSX - preserve exact formatting
25
+ for (let i = startLine; i <= endLine; i++) {
26
+ if (i === startLine) {
27
+ originalJsx += lines[i].substring(startCol);
28
+ }
29
+ else if (i === endLine) {
30
+ originalJsx += '\n' + lines[i].substring(0, endCol);
31
+ }
32
+ else {
33
+ originalJsx += '\n' + lines[i];
34
+ }
35
+ }
36
+ }
37
+ // Store the original content in node data
38
+ jsxNode.data = jsxNode.data || {};
39
+ jsxNode.data.originalJsx = originalJsx;
40
+ jsxNode.data.preserveOriginal = true;
41
+ }
42
+ // Handle self-closing tags
43
+ if (!jsxNode.children || jsxNode.children.length === 0) {
44
+ jsxNode.selfClosing = true;
45
+ }
46
+ });
47
+ };
48
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Markdown Formatter Settings - 7 Core Rules
3
+ * Each option can be toggled on/off independently
4
+ */
5
+ import type { FormatterSettings } from './types.js';
6
+ export declare const formatterSettings: FormatterSettings;
7
+ export declare function getEnabledRules(): Partial<FormatterSettings>;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Markdown Formatter Settings - 7 Core Rules
3
+ * Each option can be toggled on/off independently
4
+ */
5
+ export const formatterSettings = {
6
+ // Rule 1: Add 1 empty line between markdown elements (headings, paragraphs, lists)
7
+ addEmptyLineBetweenElements: {
8
+ enabled: true,
9
+ description: 'Add single empty line between markdown elements',
10
+ },
11
+ // Rule 2: Format multi-line JSX/HTML components with proper indentation
12
+ formatMultiLineJsx: {
13
+ enabled: true,
14
+ description: 'Format JSX/HTML with proper indentation',
15
+ indentSize: 2,
16
+ // Components to ignore (preserve their formatting completely)
17
+ ignoreComponents: [],
18
+ },
19
+ // Rule 3: Format all HTML blocks within MDX using Prettier
20
+ formatHtmlBlocksInMdx: {
21
+ enabled: true,
22
+ description: 'Format all HTML blocks within MDX using Prettier',
23
+ formatterConfig: {
24
+ parser: 'html',
25
+ tabWidth: 2,
26
+ useTabs: false,
27
+ },
28
+ },
29
+ // Rule 4: Convert single-line JSX with multiple props to multi-line format
30
+ expandSingleLineJsx: {
31
+ enabled: false, // disabled until inline JSX content-loss bug is fixed
32
+ description: 'Expand single-line JSX components with multiple props to multi-line',
33
+ propsThreshold: 2, // Expand if this many props or more
34
+ // Also respects ignoreComponents from formatMultiLineJsx
35
+ },
36
+ // Rule 5: Indent content inside JSX container components (DISABLED)
37
+ // Disabled: We prefer flat formatting for simplicity in MDX files
38
+ indentJsxContent: {
39
+ enabled: false,
40
+ description: 'Add indentation to content inside JSX components like <Outro>',
41
+ indentSize: 2,
42
+ // Components that should have their content indented
43
+ containerComponents: [],
44
+ },
45
+ // Rule 6: Add empty lines inside block JSX components
46
+ // Adds empty lines after opening tag and before closing tag for better readability
47
+ addEmptyLinesInBlockJsx: {
48
+ enabled: true,
49
+ description: 'Add empty lines after opening and before closing tags in block JSX components',
50
+ // Components that should have empty lines added
51
+ blockComponents: [],
52
+ },
53
+ // Rule 7: Format YAML frontmatter
54
+ formatYamlFrontmatter: {
55
+ enabled: true,
56
+ description: 'Format YAML frontmatter using proper YAML formatting rules',
57
+ // YAML formatting options
58
+ indent: 2, // Number of spaces for indentation
59
+ lineWidth: 100, // Maximum line width for folded strings
60
+ quotingType: '"', // Quote type for strings that need quoting: '"' or "'"
61
+ forceQuotes: false, // Force quotes on all string values
62
+ noCompatMode: true, // Use YAML 1.2 spec (not 1.1)
63
+ },
64
+ // Rule 8: Preserve Docusaurus admonitions
65
+ preserveAdmonitions: {
66
+ enabled: true,
67
+ description: 'Keep Docusaurus admonitions (:::note, :::tip, etc.) intact',
68
+ },
69
+ // Rule 9: Error handling behavior
70
+ errorHandling: {
71
+ throwOnError: false, // Don't throw error, return original content with warning
72
+ description: 'How to handle parsing errors - return original or throw',
73
+ },
74
+ // Rule 10: Auto-detect indentation from file content
75
+ autoDetectIndent: {
76
+ enabled: false,
77
+ description: 'Automatically detect indentation style from file content',
78
+ fallbackIndentSize: 2, // Default indent size if detection fails
79
+ fallbackIndentType: 'space', // Default indent type ('space' or 'tab')
80
+ minConfidence: 0.7, // Minimum confidence score to use detected indentation
81
+ },
82
+ };
83
+ // Export function to get only enabled rules
84
+ export function getEnabledRules() {
85
+ return Object.entries(formatterSettings)
86
+ .filter(([_, config]) => config.enabled)
87
+ .reduce((acc, [key, config]) => {
88
+ acc[key] = config;
89
+ return acc;
90
+ }, {});
91
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * SpecificFormatter - Applies only specific formatting rules to MDX content
3
+ * Does NOT reformat everything, only applies targeted fixes
4
+ */
5
+ import type { FormatterSettings } from './types.js';
6
+ export declare class SpecificFormatter {
7
+ private content;
8
+ private lines;
9
+ settings: FormatterSettings;
10
+ constructor(content: string, settings?: FormatterSettings | null);
11
+ format(): Promise<string>;
12
+ /**
13
+ * Rule 1: Add 1 empty line between elements
14
+ */
15
+ addEmptyLineBetweenElements(content: string): string;
16
+ shouldAddEmptyLine(currentLine: string, nextLine: string): boolean;
17
+ /**
18
+ * Combined JSX formatting - handles structure and content indentation together
19
+ */
20
+ formatJsxStructure(content: string): string;
21
+ /**
22
+ * Rule 4: Expand single-line JSX with 2+ props
23
+ */
24
+ expandSingleLineJsx(content: string): string;
25
+ parseJsxProps(propsString: string): string[];
26
+ /**
27
+ * Rule 7: Validate MDX and throw errors on invalid content
28
+ */
29
+ validateMdx(content: string): boolean;
30
+ }