Skip to content

Commit f0dc2e6

Browse files
committed
fix: add heading hierarchy check and fix set -e crash in validate_docs.sh
Two issues fixed: 1. **Bug fix**: `((WARNINGS++))` and `((ERRORS++))` with `set -e` causes the script to exit prematurely when the counter is 0, because bash treats `((0))` as falsy (exit code 1). Replaced with `WARNINGS=$((WARNINGS + 1))` which always succeeds. 2. **New check**: Added TYPO3 heading hierarchy validation that detects: - First section heading not using `=` (h2) — catches the common issue where sections extracted from a parent page retain their old heading levels (e.g. `-` for h3 instead of `=` for h2) - Non-standard underline characters (e.g. `^` instead of `=/-/~`) - Skipped heading levels (e.g. h2 directly to h4 without h3) The check follows TYPO3 heading convention: - h1: `=` above and below (page title) - h2: `=` below only (sections) - h3: `-` below only (subsections) - h4: `~` below only (sub-subsections) This would have caught the heading issues reported in TYPO3-Documentation/TYPO3CMS-Guide-HowToDocument#510. Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
1 parent 2afe8e7 commit f0dc2e6

1 file changed

Lines changed: 102 additions & 3 deletions

File tree

skills/typo3-docs/scripts/validate_docs.sh

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ if command -v rst2html.py &> /dev/null; then
6060
while IFS= read -r -d '' file; do
6161
if ! rst2html.py --strict "$file" > /dev/null 2>&1; then
6262
echo "❌ Syntax error in: $file"
63-
((ERRORS++))
63+
ERRORS=$((ERRORS + 1))
6464
fi
6565
done < <(find "$DOC_DIR" -name "*.rst" -print0)
6666

@@ -97,18 +97,117 @@ done < <(find "$DOC_DIR" -name "*.rst" -print0)
9797
while IFS= read -r -d '' file; do
9898
if ! file -b --mime-encoding "$file" | grep -q utf-8; then
9999
echo "⚠️ Non-UTF-8 encoding in: $file"
100-
((WARNINGS++))
100+
WARNINGS=$((WARNINGS + 1))
101101
fi
102102
done < <(find "$DOC_DIR" -name "*.rst" -print0)
103103

104104
# Check for trailing whitespace
105105
while IFS= read -r -d '' file; do
106106
if grep -q '[[:space:]]$' "$file"; then
107107
echo "⚠️ Trailing whitespace in: $file"
108-
((WARNINGS++))
108+
WARNINGS=$((WARNINGS + 1))
109109
fi
110110
done < <(find "$DOC_DIR" -name "*.rst" -print0)
111111

112+
# Check TYPO3 heading hierarchy (= for h1/h2, - for h3, ~ for h4)
113+
echo ""
114+
echo "Checking heading hierarchy..."
115+
HEADING_ERRORS=0
116+
while IFS= read -r -d '' file; do
117+
RESULT=$(python3 -c "
118+
import sys
119+
120+
# TYPO3 heading convention:
121+
# h1: = above AND below (overline)
122+
# h2: = below only
123+
# h3: - below only
124+
# h4: ~ below only
125+
# h5: \" below only
126+
# h6: ' below only
127+
EXPECTED_ORDER = ['=', '-', '~', '\"', \"'\"]
128+
129+
with open('$file') as f:
130+
lines = f.readlines()
131+
132+
headings = []
133+
i = 0
134+
while i < len(lines):
135+
line = lines[i].rstrip()
136+
if line and len(line) >= 3 and all(c == line[0] for c in line) and line[0] in '=-~\"^#\\'':
137+
char = line[0]
138+
# Check if this is an overline (title) or underline
139+
if i + 2 < len(lines):
140+
next_line = lines[i+1].rstrip()
141+
next_next = lines[i+2].rstrip()
142+
if (next_line and not all(c == next_line[0] for c in next_line)
143+
and next_next and all(c == next_next[0] for c in next_next)
144+
and next_next[0] == char):
145+
# Overline+underline = title (h1), skip both
146+
headings.append(('h1', char, next_line, i+1))
147+
i += 3
148+
continue
149+
# Underline only - check previous line for title text
150+
if i > 0:
151+
prev = lines[i-1].rstrip()
152+
if prev and not all(c == prev[0] for c in prev):
153+
headings.append(('section', char, prev, i))
154+
i += 1
155+
156+
if not headings:
157+
sys.exit(0)
158+
159+
# Check that section headings follow TYPO3 convention
160+
errors = []
161+
sections = [h for h in headings if h[0] == 'section']
162+
163+
# Check 1: First section heading must use = (h2)
164+
if sections and sections[0][1] != '=':
165+
char = sections[0][1]
166+
title = sections[0][2]
167+
lineno = sections[0][3]
168+
errors.append(f' L{lineno+1}: \"{title}\" - first section heading must use \"=\" (h2), not \"{char}\"')
169+
170+
# Check 2: Detect nesting violations (e.g. h4 directly under h2, skipping h3)
171+
# Track the current heading depth stack
172+
depth_stack = [] # stack of EXPECTED_ORDER indices
173+
for kind, char, title, lineno in headings:
174+
if kind == 'h1':
175+
depth_stack = []
176+
continue
177+
if char not in EXPECTED_ORDER:
178+
errors.append(f' L{lineno+1}: \"{title}\" uses non-standard underline char \"{char}\"')
179+
continue
180+
char_idx = EXPECTED_ORDER.index(char)
181+
# Pop stack back to find where this heading fits
182+
while depth_stack and depth_stack[-1] >= char_idx:
183+
depth_stack.pop()
184+
# Check for skipped levels (e.g. jumping from = to ~ without -)
185+
if depth_stack:
186+
parent_idx = depth_stack[-1]
187+
if char_idx > parent_idx + 1:
188+
skipped = EXPECTED_ORDER[parent_idx + 1]
189+
errors.append(f' L{lineno+1}: \"{title}\" uses \"{char}\" (h{char_idx+2}) directly under \"{EXPECTED_ORDER[parent_idx]}\" (h{parent_idx+2}), skipping \"{skipped}\" (h{parent_idx+3})')
190+
depth_stack.append(char_idx)
191+
192+
for e in errors:
193+
print(e)
194+
" 2>/dev/null)
195+
if [ -n "$RESULT" ]; then
196+
echo "❌ Heading hierarchy issue in: $(basename "$file")"
197+
echo "$RESULT"
198+
HEADING_ERRORS=$((HEADING_ERRORS + 1))
199+
fi
200+
done < <(find "$DOC_DIR" -name "*.rst" -print0)
201+
202+
if [ $HEADING_ERRORS -eq 0 ]; then
203+
echo "✅ Heading hierarchy follows TYPO3 convention"
204+
else
205+
echo ""
206+
echo "❌ Found $HEADING_ERRORS files with heading hierarchy issues"
207+
echo " TYPO3 heading order: = (h1 title, above+below), = (h2), - (h3), ~ (h4)"
208+
WARNINGS=$((WARNINGS + HEADING_ERRORS))
209+
fi
210+
112211
echo ""
113212
if [ $WARNINGS -eq 0 ]; then
114213
echo "✅ No common issues found"

0 commit comments

Comments
 (0)