Skip to content

fix(parser): require whitespace after the ":" footer separator - #57

Open
svyatov wants to merge 1 commit into
conventional-commits:mainfrom
svyatov:fix/footer-separator-whitespace
Open

fix(parser): require whitespace after the ":" footer separator#57
svyatov wants to merge 1 commit into
conventional-commits:mainfrom
svyatov:fix/footer-separator-whitespace

Conversation

@svyatov

@svyatov svyatov commented Jul 22, 2026

Copy link
Copy Markdown

Spec rule 8 allows only the :<space> and <space># separator forms, but footer() accepts a bare :, so External-Id:1337 parses as a footer. #47 reports the gap in the README grammar; this fixes the grammar and the parser together, the way #39 was scoped.

If you would rather not move parser behavior, say so, and I will cut this down to the README change alone. That is up to you.

The change

The <whitespace>* step in footer() becomes required for ":" and stays optional for " #", which carries its own space. Returning an Error is what the callers already expect: preFooter() backtracks and body() reads the line as <text>.

The AST is untouched. <separator> keeps the value ":" with <whitespace> as a sibling node, so every currently-valid parse is byte-identical, toConventionalChangelogFormat keeps working, and the multiple-space case does not split awkwardly.

In the README, <footer> becomes two productions so the " #" asymmetry is explicit, matching how <summary> at line 68 already inlines its literal ":".

Behavior change

Input that parsed as a footer now parses as body text:

parser('fix: some stuff\n\nExternal-Id:1337')
// was: footer > token > type "External-Id"
// now: body > text "External-Id:1337"

<pre-footer> is all or nothing, so one disqualified line demotes the whole block:

parser('fix: some stuff\n\nAuthor: @bcoe\nExternal-Id:1337')
// "Author: @bcoe" is demoted to body text too, and its references entry is lost

That demotion is existing behavior, this just widens what triggers it. Nothing disappears from the tree. BREAKING CHANGE notes survive it, because toConventionalChangelogFormat reads the marker under <body> as well as under <token>, so BREAKING CHANGE:no space still reaches cc.notes.

Filed as plain fix, giving 0.4.2 under bump-minor-pre-major: true: no API, node type, or field changed, only which strings qualify. Re-tag it fix! if you would rather have the minor bump.

What deliberately did not change

summary() keeps its ":" <whitespace>* lenience, so feat(tree):no whitespaces here still parses. Rules 1 and 5 impose the same "colon and space" there, but that is a separate question with much larger fallout, and #47 does not ask for it. The BREAKING CHANGE: branch in body() is likewise untouched, being a body-level extension rather than a footer.

Also unrelated to #41: this is the :<space> requirement, not the git-trailer whitespace rules.

Tests

parses footer tokens without whitespaces after separator is the one existing test whose verdict flips. It is renamed, converted to plain assertions, and its 218-line snapshot block deleted by hand, since chai-jest-snapshot does not prune orphans. I did not run test:snap, so the other 20 blocks are byte-identical. Two footer tests are added: the " #" form, and the block-demotion case above.

Locally, 36 passing, standard clean, and npm run coverage clears the .nycrc thresholds. Two of the three new assertions fail without the parser change.

Closes #47

Spec rule 8 allows only the ":" <space> and <space> "#" separator forms,
but footer() accepted a bare ":", so "External-Id:1337" parsed as a
footer. It now parses as body text.

<pre-footer> is all or nothing, so one disqualified line demotes the whole
block: in "Author: @bcoe\nExternal-Id:1337" the Author footer becomes body
text as well, and its references entry is lost. Nothing disappears from
the tree, and BREAKING CHANGE notes survive the demotion because
toConventionalChangelogFormat reads the marker under <body> as well as
under <token>.

summary() keeps its ":" <whitespace>* lenience. Rules 1 and 5 impose the
same requirement there, but tightening it would reject "feat:no space",
which conventional-commits#47 does not ask for.

Closes conventional-commits#47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Grammar does not enforce whitespace after : token in footers

1 participant