Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 39 additions & 21 deletions src/commit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,31 +421,49 @@ export function parseConventionalCommits(
for (const commitMessage of splitMessages(
preprocessCommitMessage(commit)
)) {
const pushParsedCommit = (
parsedCommit: parser.ConventionalChangelogCommit
) => {
const breaking =
parsedCommit.notes.filter(note => note.title === 'BREAKING CHANGE')
.length > 0;
conventionalCommits.push({
sha: commit.sha,
message: parsedCommit.header,
files: commit.files,
pullRequest: commit.pullRequest,
type: parsedCommit.type,
scope: parsedCommit.scope,
bareMessage: parsedCommit.subject,
notes: parsedCommit.notes,
references: parsedCommit.references,
breaking,
});
};
try {
for (const parsedCommit of parseCommits(commitMessage)) {
const breaking =
parsedCommit.notes.filter(note => note.title === 'BREAKING CHANGE')
.length > 0;
conventionalCommits.push({
sha: commit.sha,
message: parsedCommit.header,
files: commit.files,
pullRequest: commit.pullRequest,
type: parsedCommit.type,
scope: parsedCommit.scope,
bareMessage: parsedCommit.subject,
notes: parsedCommit.notes,
references: parsedCommit.references,
breaking,
});
pushParsedCommit(parsedCommit);
}
} catch (_err) {
logger.debug(
`commit could not be parsed: ${commit.sha} ${
commit.message.split('\n')[0]
}`
);
logger.debug(`error message: ${_err}`);
// Full-message parsing failed. The @conventional-commits/parser
// grammar is strict about body content — e.g. an unbalanced `(` on
// a wrapped line will throw `unexpected token '\n' ... valid
// tokens [)]`. Fall back to parsing just the header so the commit
// still appears in the changelog. Footer-derived metadata
// (BREAKING CHANGE notes, issue references) is lost on this path.
const header = commitMessage.split('\n')[0];
try {
for (const parsedCommit of parseCommits(header)) {
pushParsedCommit(parsedCommit);
}
logger.warn(
`commit body could not be parsed, used header only: ${commit.sha} ${header}`
);
logger.debug(`error message: ${_err}`);
} catch (_headerErr) {
logger.debug(`commit could not be parsed: ${commit.sha} ${header}`);
logger.debug(`error message: ${_err}`);
}
}
}
}
Expand Down
26 changes: 26 additions & 0 deletions test/commits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,32 @@ describe('parseConventionalCommits', () => {
expect(conventionalCommits[1].bareMessage).to.eql('another feature');
});

it('falls back to header-only parsing when the body is unparseable', async () => {
// A body line that opens `(` without closing it on the same line trips
// the @conventional-commits/parser grammar with
// `unexpected token '\n' ... valid tokens [)]`. This commonly happens
// when a GitHub PR body wraps a function call across two lines.
// Without the fallback, the commit silently disappears from the
// changelog.
const message = [
'feat(parser): handle wrapped function calls',
'',
'Body wraps a call:',
'computeValue(longArgumentName',
'= true)',
'so the upstream parser throws on the unmatched paren.',
].join('\n');
const commits = [buildMockCommit(message)];
const conventionalCommits = parseConventionalCommits(commits);
expect(conventionalCommits).lengthOf(1);
expect(conventionalCommits[0].type).to.equal('feat');
expect(conventionalCommits[0].scope).to.equal('parser');
expect(conventionalCommits[0].bareMessage).to.equal(
'handle wrapped function calls'
);
expect(conventionalCommits[0].breaking).to.be.false;
});

it('handles a special commit separator', async () => {
const commits = [buildCommitFromFixture('multiple-commits-with-separator')];
const conventionalCommits = parseConventionalCommits(commits);
Expand Down
Loading