fix(docs): markdown code fence violations and formatting

This commit is contained in:
Jesús Pérez 2026-01-14 04:55:23 +00:00
parent 17ef93ed23
commit 35acc1987e

103
scripts/fix-markdown-fences.nu Normal file → Executable file
View File

@ -1,19 +1,18 @@
#!/usr/bin/env nu
# Fix Markdown Issues: Newlines + Code Fence Violations (MD040 + CommonMark)
# Fix Markdown Issues: Newlines + Closing Code Fence Violations
# Handles:
# 1. Literal \n escape sequences → actual newlines
# 2. Closing fences with language specifiers (malformed)
# 3. Opening fences without language specifiers (missing)
#
# Usage:
# nu fix-markdown-fences.nu # Fix: newlines + closing fences
# nu fix-markdown-fences.nu # Fix all: newlines + closing fences
# nu fix-markdown-fences.nu --dry-run # Preview without changes
# nu fix-markdown-fences.nu --phase newlines # Only fix literal \n
# nu fix-markdown-fences.nu --phase closing # Only fix closing fences
# nu fix-markdown-fences.nu --dry-run --report # Show detailed report
#
# For opening fences: use sed instead
# find . -name "*.md" -exec sed -i 's/^```$/```text/g' {} \;
# For opening fences (manual):
# find . -name '*.md' -exec sed -i '' 's/^```$/```text/g; s/^```[a-z]*$/```/g' {} \;
#
# Phases:
# newlines - Fix literal \n escape sequences → actual newlines
@ -22,12 +21,12 @@
def main [
--dry-run # Preview without making changes
--phase: string = "all" # Phase: newlines|closing|all
--phase: string = "all" # Phase: cleanup|newlines|closing|opening|all
--report # Show detailed before/after report
] {
# Validate phase argument
if $phase !~ '^(newlines|closing|all)$' {
print $"Error: Invalid phase '$phase'. Must be: newlines, closing, or all"
if $phase !~ '^(cleanup|newlines|closing|opening|all)$' {
print $"Error: Invalid phase '$phase'. Must be: cleanup, newlines, closing, opening, or all"
exit 1
}
@ -44,6 +43,7 @@ def main [
print ""
# Track statistics
mut total_cleanup_fixed = 0
mut total_newlines_fixed = 0
mut total_closing_fixed = 0
mut total_opening_fixed = 0
@ -53,11 +53,20 @@ def main [
for file in $md_files {
let original_content = open $file
mut modified_content = $original_content
mut cleanup_fixed = 0
mut newlines_fixed = 0
mut closing_fixed = 0
mut opening_fixed = 0
# Phase 0: Fix literal \n escape sequences
# Phase -1: Clean up corrupted {$detected_lang} literals (CRITICAL)
if ($phase == "cleanup" or $phase == "all") {
let cleanup_result = cleanup-corrupted-fences $modified_content
$cleanup_fixed = $cleanup_result.fixed_count
$total_cleanup_fixed += $cleanup_fixed
$modified_content = $cleanup_result.content
}
# Phase 0: Fix literal \n escape sequences (Nushell - SAFE)
if ($phase == "newlines" or $phase == "all") {
if ($modified_content | str contains '\\n') {
let newlines_result = fix-literal-newlines $modified_content
@ -67,7 +76,7 @@ def main [
}
}
# Phase 1: Fix closing fences
# Phase 1: Fix closing fences (Nushell - SAFE)
if ($phase == "closing" or $phase == "all") {
let closing_result = fix-closing-fences $modified_content
$closing_fixed = $closing_result.fixed_count
@ -76,16 +85,15 @@ def main [
}
# Phase 2: Fix opening fences with language detection
# NOTE: Disabled - use sed instead due to Nushell string join newline corruption
# if ($phase == "opening" or $phase == "all") {
# let opening_result = fix-opening-fences $modified_content $file
# $opening_fixed = $opening_result.fixed_count
# $total_opening_fixed += $opening_fixed
# $modified_content = $opening_result.content
# }
if ($phase == "opening" or $phase == "all") {
let opening_result = fix-opening-fences $modified_content $file
$opening_fixed = $opening_result.fixed_count
$total_opening_fixed += $opening_fixed
$modified_content = $opening_result.content
}
# Write changes if not dry-run AND if there were any fixes
let has_changes = ($newlines_fixed > 0) or ($closing_fixed > 0) or ($opening_fixed > 0)
let has_changes = ($cleanup_fixed > 0) or ($newlines_fixed > 0) or ($closing_fixed > 0) or ($opening_fixed > 0)
if $has_changes {
if (not $dry_run) {
$modified_content | save --force $file
@ -94,6 +102,9 @@ def main [
if $report {
print $"✏️ Modified: ($file)"
if $cleanup_fixed > 0 {
print $" Corrupted literals fixed: ($cleanup_fixed)"
}
if $newlines_fixed > 0 {
print $" Literal newlines fixed: ($newlines_fixed)"
}
@ -111,7 +122,8 @@ def main [
print "═════════════════════════════════════"
print "Summary"
print "═════════════════════════════════════"
print $"Files modified: ($files_modified)"
print $"Files scanned: ($md_files | length)"
print $"Corrupted literals fixed: ($total_cleanup_fixed)"
print $"Literal newlines fixed: ($total_newlines_fixed)"
print $"Closing fences fixed: ($total_closing_fixed)"
print $"Opening fences fixed: ($total_opening_fixed)"
@ -123,8 +135,7 @@ def main [
} else {
print "✅ Changes applied successfully"
print "Run: git diff # Review changes"
print "Run: nu scripts/check-malformed-fences.nu # Validate closing fences"
print "Run: markdownlint-cli2 '**/*.md' --fix # Validate and fix remaining issues"
print "Run: markdownlint-cli2 '**/*.md' # Validate (MD040, MD060)"
}
}
@ -136,6 +147,44 @@ def fix-literal-newlines [content] {
}
}
# Clean up corrupted {$detected_lang} literals and remnant text lines
def cleanup-corrupted-fences [content] {
let lines = $content | lines
mut fixed_lines = []
mut fixed_count = 0
for idx in (0..<($lines | length)) {
let line = $lines | get $idx
# Check for corrupted opening fence with {$detected_lang}
if ($line =~ '^```\{?\$detected_lang\}?$') {
# Replace with clean ```
$fixed_lines = ($fixed_lines | append '```')
$fixed_count += 1
} else {
# Check if this is a remnant "text" line after a language-tagged opening fence
let is_text_remnant = (
$idx > 0 and
($lines | get ($idx - 1)) =~ '^```\w+' and
$line == 'text'
)
if $is_text_remnant {
# Skip this garbage line
$fixed_count += 1
} else {
# Keep the line
$fixed_lines = ($fixed_lines | append $line)
}
}
}
{
content: ($fixed_lines | str join "\n")
fixed_count: $fixed_count
}
}
# Discover all markdown files with proper exclusions
def discover-markdown-files [] {
glob **/*.md
@ -187,7 +236,7 @@ def fix-closing-fences [content] {
}
{
content: ($fixed_lines | str join '\n')
content: ($fixed_lines | str join "\n")
fixed_count: $fixed_count
}
}
@ -219,15 +268,13 @@ def fix-opening-fences [content, file_path] {
# Get context before fence (3 lines)
let context_start = if ($idx > 3) { $idx - 3 } else { 0 }
let context_before = $lines | skip $context_start | first ($idx - $context_start) | str join '\n'
let context_before = $lines | skip $context_start | first ($idx - $context_start) | str join "\n"
# Detect language
let detected_lang = detect-language $content_after $context_before $file_path
# Add language to fence
# NOTE: Using placeholder to avoid Nushell string join issues with newlines
# Will be processed with sed post-fix
$fixed_lines = ($fixed_lines | append $'```{$detected_lang}')
# Add language to fence with detected language (using double quotes for interpolation)
$fixed_lines = ($fixed_lines | append $"```($detected_lang)")
$fixed_count += 1
} else {
# Opening fence WITH language → no fix needed
@ -246,7 +293,7 @@ def fix-opening-fences [content, file_path] {
}
{
content: ($fixed_lines | str join '\n')
content: ($fixed_lines | str join "\n")
fixed_count: $fixed_count
}
}