Skip to content

Commit a1272fb

Browse files
committed
fix(heading-title-case): skip alphanumeric identifiers (e.g. 4a, v2) for title-case
Words like "4a" were incorrectly flagged as needing capitalization. Skip segments that contain both a digit and a letter (alphanumeric identifiers). Add unit tests, positive fixture heading, and functional fix test.
1 parent 809a866 commit a1272fb

6 files changed

Lines changed: 222 additions & 167 deletions

File tree

‎Makefile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ test-rules:
131131
@node --test test/markdownlint-rules/*.test.js
132132

133133
# Unit test coverage for markdownlint-rules/*.js (fails if any file < 90% lines/statements).
134-
# Requires: Node.js, npm; run 'npm install' first.
134+
# Requires: Node.js, npm; run 'npm install' first. Use Node 20 or 22 for coverage (c8 fails on Node 25).
135135
test-rules-coverage:
136136
@command -v node >/dev/null 2>&1 || { \
137137
echo "Error: node not found. Install Node.js and run npm install."; \

‎markdownlint-rules/heading-title-case.js‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,17 @@ function isPhaseLabelSegment(j, core, previousWordCore) {
172172
return j === 0 && core.length === 1 && /^[A-Za-z]$/.test(core) && LABEL_PARENT_WORDS.has(previousWordCore);
173173
}
174174

175-
/** True if this segment should be skipped for title-case (entity, empty, non-alpha, or scientific notation). */
175+
/** Alphanumeric identifier (e.g. 4a, v2, 2b); skip so "4a" is not flagged for capitalization. */
176+
function isAlphanumericIdentifier(core) {
177+
return /[0-9]/.test(core) && /[a-zA-Z]/.test(core);
178+
}
179+
180+
/** True if this segment should be skipped for title-case (entity, empty, non-alpha, scientific notation, or alphanumeric identifier). */
176181
function shouldSkipSegmentForTitleCase(rawSeg, core) {
177182
return isHtmlEntity(rawSeg)
178183
|| !core || !/[a-zA-Z]/.test(core)
179-
|| RE_SCIENTIFIC.test(core);
184+
|| RE_SCIENTIFIC.test(core)
185+
|| isAlphanumericIdentifier(core);
180186
}
181187

182188
/**

‎md_test_files/positive_general.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ Should not catch.
154154

155155
Scientific notation (e.g. 5e, 1e6) in headings is not title-cased; the "e" is left as-is.
156156

157+
### 9. Section 4a and Results
158+
159+
Alphanumeric identifiers (e.g. 4a, v2, 2b) in headings are not title-cased; they are left as-is.
160+
157161
## Zero-Indexed Heading Numbering
158162

159163
When the first numbered heading in a section starts at 0, the rule treats that section as 0-based and does not report errors.

0 commit comments

Comments
 (0)