Skip to content

Commit c4e607f

Browse files
vaindclaude
andcommitted
refactor: Handle missing UNRELEASED sections and improve flexibility
Enhanced changelog insertion logic to handle all possible scenarios: - **No UNRELEASED section**: Creates full "## Unreleased\n### Section\n- Entry" - **UNRELEASED but no subsection**: Creates "### Section\n- Entry" - **Both exist**: Just adds "- Entry" Changes: - Refactored findChangelogInsertionPoint() to return insertContent type - Updated generateChangelogSuggestion() with switch statement for content types - Added 8 new edge case tests (43 total tests, all passing) - Handles empty changelogs, missing sections, mixed case headers This makes the feature work with any changelog state, not just existing structured changelogs. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 5e5f5d8 commit c4e607f

File tree

2 files changed

+204
-38
lines changed

2 files changed

+204
-38
lines changed

danger/dangerfile-utils.js

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ function extractPRFlavor(prTitle, prBranchRef) {
8686
return "";
8787
}
8888

89-
/// Find insertion point for changelog entry in a specific section
89+
/// Find insertion point and determine what content needs to be inserted
9090
function findChangelogInsertionPoint(changelogContent, sectionName) {
9191
const lines = changelogContent.split('\n');
9292

@@ -99,15 +99,38 @@ function findChangelogInsertionPoint(changelogContent, sectionName) {
9999
}
100100
}
101101

102+
// Case 1: No Unreleased section exists
102103
if (unreleasedIndex === -1) {
103-
return null; // No Unreleased section found
104+
// Find first ## section or top of changelog to insert before it
105+
let insertionPoint = 0;
106+
107+
// Skip title and initial content, look for first version section
108+
for (let i = 0; i < lines.length; i++) {
109+
if (lines[i].trim().match(/^##\s+/)) {
110+
insertionPoint = i;
111+
break;
112+
}
113+
}
114+
115+
// If no version sections exist, insert at end
116+
if (insertionPoint === 0) {
117+
insertionPoint = lines.length;
118+
}
119+
120+
return {
121+
lineNumber: insertionPoint + 1, // 1-indexed for GitHub API
122+
insertContent: 'unreleased-and-section'
123+
};
104124
}
105125

106-
// Find the target subsection (e.g., "### Features")
126+
// Case 2: Unreleased section exists, find the target subsection
107127
let sectionIndex = -1;
128+
let nextSectionIndex = lines.length; // End of file by default
129+
108130
for (let i = unreleasedIndex + 1; i < lines.length; i++) {
109131
// Stop if we hit another main section (##)
110132
if (lines[i].trim().match(/^##\s+/)) {
133+
nextSectionIndex = i;
111134
break;
112135
}
113136

@@ -118,40 +141,37 @@ function findChangelogInsertionPoint(changelogContent, sectionName) {
118141
}
119142
}
120143

144+
// Case 3: Subsection doesn't exist, need to create it within Unreleased
121145
if (sectionIndex === -1) {
122-
// Section doesn't exist, we need to create it
123-
// Find insertion point after "## Unreleased"
146+
// Find insertion point after "## Unreleased" but before next main section
124147
let insertAfter = unreleasedIndex;
125148

126149
// Skip empty lines after "## Unreleased"
127-
while (insertAfter + 1 < lines.length && lines[insertAfter + 1].trim() === '') {
150+
while (insertAfter + 1 < nextSectionIndex && lines[insertAfter + 1].trim() === '') {
128151
insertAfter++;
129152
}
130153

131154
return {
132155
lineNumber: insertAfter + 1, // 1-indexed for GitHub API
133-
createSection: true,
134-
sectionName: sectionName
156+
insertContent: 'section-and-entry'
135157
};
136158
}
137159

138-
// Section exists, find first bullet point or insertion point
160+
// Case 4: Both Unreleased and subsection exist, just add entry
139161
let insertionPoint = sectionIndex + 1;
140162

141163
// Skip empty lines after section header
142-
while (insertionPoint < lines.length && lines[insertionPoint].trim() === '') {
164+
while (insertionPoint < nextSectionIndex && lines[insertionPoint].trim() === '') {
143165
insertionPoint++;
144166
}
145167

146-
// If next line is a bullet point, insert before it
147-
// If it's another section or end of file, insert here
148168
return {
149169
lineNumber: insertionPoint + 1, // 1-indexed for GitHub API
150-
createSection: false
170+
insertContent: 'entry-only'
151171
};
152172
}
153173

154-
/// Generate suggestion text for changelog entry
174+
/// Generate suggestion text for changelog entry based on what needs to be inserted
155175
function generateChangelogSuggestion(prTitle, prNumber, prUrl, sectionName, insertionInfo) {
156176
// Clean up PR title (remove conventional commit prefix if present)
157177
const cleanTitle = prTitle
@@ -162,12 +182,22 @@ function generateChangelogSuggestion(prTitle, prNumber, prUrl, sectionName, inse
162182

163183
const bulletPoint = `- ${cleanTitle} ([#${prNumber}](${prUrl}))`;
164184

165-
if (insertionInfo.createSection) {
166-
// Need to create the section
167-
return `\n### ${sectionName}\n\n${bulletPoint}`;
168-
} else {
169-
// Just add the bullet point
170-
return bulletPoint;
185+
switch (insertionInfo.insertContent) {
186+
case 'unreleased-and-section':
187+
// Need to create both Unreleased section and subsection
188+
return `## Unreleased\n\n### ${sectionName}\n\n${bulletPoint}\n`;
189+
190+
case 'section-and-entry':
191+
// Need to create subsection within existing Unreleased
192+
return `\n### ${sectionName}\n\n${bulletPoint}`;
193+
194+
case 'entry-only':
195+
// Just add the bullet point to existing section
196+
return bulletPoint;
197+
198+
default:
199+
// Fallback to entry-only
200+
return bulletPoint;
171201
}
172202
}
173203

0 commit comments

Comments
 (0)