Skip to content

Commit f0529da

Browse files
authored
Pass default changelog renderers through to custom renders (#1154)
1 parent 0253a54 commit f0529da

6 files changed

Lines changed: 111 additions & 89 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Pass default changelog renderers through to custom renders. Also escape the character `<` in changelog entries when rendering if it's definitely not inside a code block.",
4+
"packageName": "beachball",
5+
"email": "elcraig@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

src/__tests__/changelog/__snapshots__/renderPackageChangelog.test.ts.snap

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,5 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`changelog renderers - renderChangeTypeHeader has correct grouped output 1`] = `"### Minor changes"`;
4-
5-
exports[`changelog renderers - renderChangeTypeHeader has correct output 1`] = `"### Minor changes"`;
6-
7-
exports[`changelog renderers - renderChangeTypeSection has correct grouped output 1`] = `
8-
"### Minor changes
9-
10-
- \`bar\`
11-
- Awesome change (user1@example.com)
12-
- \`foo\`
13-
- Boring change (user2@example.com)"
14-
`;
15-
16-
exports[`changelog renderers - renderChangeTypeSection has correct output 1`] = `
17-
"### Minor changes
18-
19-
- Awesome change (user1@example.com)
20-
- Boring change (user2@example.com)"
21-
`;
22-
23-
exports[`changelog renderers - renderEntries has correct grouped output 1`] = `
24-
"- \`bar\`
25-
- Awesome change (user1@example.com)
26-
- \`foo\`
27-
- Boring change (user2@example.com)"
28-
`;
29-
30-
exports[`changelog renderers - renderEntries has correct output 1`] = `
31-
"- Awesome change (user1@example.com)
32-
- Boring change (user2@example.com)"
33-
`;
34-
35-
exports[`changelog renderers - renderEntry has correct grouped output 1`] = `"- Awesome change (user1@example.com)"`;
36-
37-
exports[`changelog renderers - renderEntry has correct output 1`] = `"- Awesome change (user1@example.com)"`;
38-
39-
exports[`changelog renderers - renderHeader has correct grouped output 1`] = `
40-
"## 1.2.3
41-
42-
Thu, 22 Aug 2019 21:20:40 GMT"
43-
`;
44-
45-
exports[`changelog renderers - renderHeader has correct output 1`] = `
46-
"## 1.2.3
47-
48-
Thu, 22 Aug 2019 21:20:40 GMT"
49-
`;
50-
513
exports[`changelog renderers - renderPackageChangelog has correct grouped output 1`] = `
524
"## 1.2.3
535
@@ -68,22 +20,6 @@ Thu, 22 Aug 2019 21:20:40 GMT
6820
- stuff (user2@example.com)"
6921
`;
7022

71-
exports[`changelog renderers - renderPackageChangelog has correct output 1`] = `
72-
"## 1.2.3
73-
74-
Thu, 22 Aug 2019 21:20:40 GMT
75-
76-
### Minor changes
77-
78-
- Awesome change (user1@example.com)
79-
- Boring change (user2@example.com)
80-
81-
### Patches
82-
83-
- Fix (user1@example.com)
84-
- stuff (user2@example.com)"
85-
`;
86-
8723
exports[`changelog renderers - renderPackageChangelog includes all change types 1`] = `
8824
"## 1.2.3
8925

src/__tests__/changelog/renderPackageChangelog.test.ts

Lines changed: 86 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import { SortedChangeTypes } from '../../changefile/changeTypes';
66

77
const { renderEntry, renderEntries, renderChangeTypeHeader, renderChangeTypeSection, renderHeader } = defaultRenderers;
88

9-
const leadingNewlineRegex = /^\n/;
10-
const trailingNewlineRegex = /\n$/;
9+
const leadingTrailingNewlineRegex = /^\n|\n$/;
1110

1211
describe('changelog renderers -', () => {
1312
function getRenderInfo(): PackageChangelogRenderInfo {
@@ -32,6 +31,7 @@ describe('changelog renderers -', () => {
3231
},
3332
previousJson: {} as ChangelogJson,
3433
renderers: { ...defaultRenderers }, // copy in case of modification
34+
defaultRenderers,
3535
};
3636
}
3737

@@ -53,87 +53,150 @@ describe('changelog renderers -', () => {
5353
return renderInfo;
5454
}
5555

56-
function doBasicTests(result: string) {
57-
expect(result).not.toMatch(leadingNewlineRegex);
58-
expect(result).not.toMatch(trailingNewlineRegex);
59-
expect(result).toMatchSnapshot();
60-
}
61-
6256
describe('renderEntry', () => {
6357
it('has correct output', async () => {
6458
const renderInfo = getRenderInfo();
6559
const result = await renderEntry(renderInfo.newVersionChangelog.comments.minor![0], renderInfo);
66-
doBasicTests(result);
60+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
61+
expect(result).toMatchInlineSnapshot(`"- Awesome change (user1@example.com)"`);
6762
});
6863

6964
it('has correct grouped output', async () => {
7065
const renderInfo = getGroupedRenderInfo();
7166
const result = await renderEntry(renderInfo.newVersionChangelog.comments.minor![0], renderInfo);
72-
doBasicTests(result);
67+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
68+
expect(result).toMatchInlineSnapshot(`"- Awesome change (user1@example.com)"`);
69+
});
70+
71+
it('escapes < outside of code blocks', async () => {
72+
const renderInfo = getRenderInfo();
73+
renderInfo.newVersionChangelog.comments.minor![0].comment = 'Add --config <file>';
74+
const result = await renderEntry(renderInfo.newVersionChangelog.comments.minor![0], renderInfo);
75+
expect(result).toMatchInlineSnapshot(`"- Add --config \\<file> (user1@example.com)"`);
76+
});
77+
78+
it('does not escape < inside code blocks', async () => {
79+
const renderInfo = getRenderInfo();
80+
renderInfo.newVersionChangelog.comments.minor![0].comment = 'Add `--config <file>`';
81+
const result = await renderEntry(renderInfo.newVersionChangelog.comments.minor![0], renderInfo);
82+
expect(result).toMatchInlineSnapshot(`"- Add \`--config <file>\` (user1@example.com)"`);
7383
});
7484
});
7585

7686
describe('renderEntries', () => {
7787
it('has correct output', async () => {
7888
const renderInfo = getRenderInfo();
7989
const result = await renderEntries('minor', renderInfo);
80-
doBasicTests(result);
90+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
91+
expect(result).toMatchInlineSnapshot(`
92+
"- Awesome change (user1@example.com)
93+
- Boring change (user2@example.com)"
94+
`);
8195
});
8296

8397
it('has correct grouped output', async () => {
8498
const renderInfo = getGroupedRenderInfo();
8599
const result = await renderEntries('minor', renderInfo);
86-
doBasicTests(result);
100+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
101+
expect(result).toMatchInlineSnapshot(`
102+
"- \`bar\`
103+
- Awesome change (user1@example.com)
104+
- \`foo\`
105+
- Boring change (user2@example.com)"
106+
`);
87107
});
88108
});
89109

90110
describe('renderChangeTypeHeader', () => {
91111
it('has correct output', async () => {
92112
const renderInfo = getRenderInfo();
93113
const result = await renderChangeTypeHeader('minor', renderInfo);
94-
doBasicTests(result);
114+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
115+
expect(result).toMatchInlineSnapshot(`"### Minor changes"`);
95116
});
96117

97118
it('has correct grouped output', async () => {
98119
const renderInfo = getGroupedRenderInfo();
99120
const result = await renderChangeTypeHeader('minor', renderInfo);
100-
doBasicTests(result);
121+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
122+
expect(result).toMatchInlineSnapshot(`"### Minor changes"`);
101123
});
102124
});
103125

104126
describe('renderChangeTypeSection', () => {
105127
it('has correct output', async () => {
106128
const renderInfo = getRenderInfo();
107129
const result = await renderChangeTypeSection('minor', renderInfo);
108-
doBasicTests(result);
130+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
131+
expect(result).toMatchInlineSnapshot(`
132+
"### Minor changes
133+
134+
- Awesome change (user1@example.com)
135+
- Boring change (user2@example.com)"
136+
`);
109137
});
110138

111139
it('has correct grouped output', async () => {
112140
const renderInfo = getGroupedRenderInfo();
113141
const result = await renderChangeTypeSection('minor', renderInfo);
114-
doBasicTests(result);
142+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
143+
expect(result).toMatchInlineSnapshot(`
144+
"### Minor changes
145+
146+
- \`bar\`
147+
- Awesome change (user1@example.com)
148+
- \`foo\`
149+
- Boring change (user2@example.com)"
150+
`);
115151
});
116152
});
117153

118154
describe('renderHeader', () => {
119155
it('has correct output', async () => {
120156
const renderInfo = getRenderInfo();
121157
const result = await renderHeader(renderInfo);
122-
doBasicTests(result);
158+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
159+
expect(result).toMatchInlineSnapshot(`
160+
"## 1.2.3
161+
162+
Thu, 22 Aug 2019 21:20:40 GMT"
163+
`);
123164
});
124165

125166
it('has correct grouped output', async () => {
126167
const renderInfo = getGroupedRenderInfo();
127168
const result = await renderHeader(renderInfo);
128-
doBasicTests(result);
169+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
170+
expect(result).toMatchInlineSnapshot(`
171+
"## 1.2.3
172+
173+
Thu, 22 Aug 2019 21:20:40 GMT"
174+
`);
129175
});
130176
});
131177

132178
describe('renderPackageChangelog', () => {
133179
it('has correct output', async () => {
134180
const renderInfo = getRenderInfo();
135181
const result = await renderPackageChangelog(renderInfo);
136-
doBasicTests(result);
182+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
183+
// Most of the package output snapshots go in a snapshot file since they're longer,
184+
// but include one inline to help as a sanity check.
185+
expect(result).toMatchInlineSnapshot(`
186+
"## 1.2.3
187+
188+
Thu, 22 Aug 2019 21:20:40 GMT
189+
190+
### Minor changes
191+
192+
- Awesome change (user1@example.com)
193+
- Boring change (user2@example.com)
194+
195+
### Patches
196+
197+
- Fix (user1@example.com)
198+
- stuff (user2@example.com)"
199+
`);
137200
});
138201

139202
it('includes all change types', async () => {
@@ -149,13 +212,15 @@ describe('changelog renderers -', () => {
149212
expect(result).toContain(`${type} change`);
150213
}
151214
}
152-
doBasicTests(result);
215+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
216+
expect(result).toMatchSnapshot();
153217
});
154218

155219
it('has correct grouped output', async () => {
156220
const renderInfo = getGroupedRenderInfo();
157221
const result = await renderPackageChangelog(renderInfo);
158-
doBasicTests(result);
222+
expect(result).not.toMatch(leadingTrailingNewlineRegex);
223+
expect(result).toMatchSnapshot();
159224
});
160225

161226
it('uses custom renderEntry', async () => {

src/changelog/renderChangelog.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { renderPackageChangelog, defaultRenderers } from './renderPackageChangel
22
import type { ChangelogOptions, PackageChangelogRenderInfo } from '../types/ChangelogOptions';
33
import type { PackageChangelog } from '../types/ChangeLog';
44

5-
export interface MarkdownChangelogRenderOptions extends Omit<PackageChangelogRenderInfo, 'renderers'> {
5+
export interface MarkdownChangelogRenderOptions
6+
extends Omit<PackageChangelogRenderInfo, 'renderers' | 'defaultRenderers'> {
67
previousContent: string;
78
changelogOptions: ChangelogOptions;
89
}
@@ -56,6 +57,7 @@ export async function renderChangelog(renderOptions: MarkdownChangelogRenderOpti
5657
...defaultRenderers,
5758
...customRenderers,
5859
},
60+
defaultRenderers: { ...defaultRenderers },
5961
};
6062

6163
const packageChangelog = await (customRenderPackageChangelog || renderPackageChangelog)(renderInfo);

src/changelog/renderPackageChangelog.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,16 @@ async function _renderEntriesBasic(
9797
}
9898

9999
function _renderEntry(entry: ChangelogEntry): string {
100-
if (entry.author === 'beachball') {
101-
return `- ${entry.comment}`;
100+
let comment = `- ${entry.comment}`;
101+
102+
if (comment.includes('<') && !/`[^`]*?</.test(comment)) {
103+
// If the comment includes a < which definitely isn't inside a code block, escape it.
104+
// (Not bothering with full backtick matching since it's generally low-consequence if wrong.)
105+
// Full HTML escaping will be handled by the final markdown renderer; we just want to prevent
106+
// comment contents that were likely not intended as HTML from breaking rendering.
107+
// e.g. "Add --config <file> option"
108+
comment = comment.replace(/</g, '\\<');
102109
}
103110

104-
return `- ${entry.comment} (${entry.author})`;
111+
return entry.author === 'beachball' ? comment : `${comment} (${entry.author})`;
105112
}

src/types/ChangelogOptions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ export interface PackageChangelogRenderInfo {
125125
* Default renderers will be included in cases where a custom option wasn't provided.
126126
*/
127127
renderers: Required<ChangelogRenderers>;
128+
129+
/**
130+
* Default renderers for each element of the changelog.
131+
*/
132+
defaultRenderers: Required<ChangelogRenderers>;
128133
}
129134

130135
export interface ChangelogRenderers {

0 commit comments

Comments
 (0)