Skip to content
Merged
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
7 changes: 7 additions & 0 deletions change/beachball-4c3bded9-403e-4f15-908b-189a039d657f.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"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.",
"packageName": "beachball",
"email": "elcraig@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`changelog renderers - renderChangeTypeHeader has correct grouped output 1`] = `"### Minor changes"`;

exports[`changelog renderers - renderChangeTypeHeader has correct output 1`] = `"### Minor changes"`;

exports[`changelog renderers - renderChangeTypeSection has correct grouped output 1`] = `
"### Minor changes

- \`bar\`
- Awesome change (user1@example.com)
- \`foo\`
- Boring change (user2@example.com)"
`;

exports[`changelog renderers - renderChangeTypeSection has correct output 1`] = `
"### Minor changes

- Awesome change (user1@example.com)
- Boring change (user2@example.com)"
`;

exports[`changelog renderers - renderEntries has correct grouped output 1`] = `
"- \`bar\`
- Awesome change (user1@example.com)
- \`foo\`
- Boring change (user2@example.com)"
`;

exports[`changelog renderers - renderEntries has correct output 1`] = `
"- Awesome change (user1@example.com)
- Boring change (user2@example.com)"
`;

exports[`changelog renderers - renderEntry has correct grouped output 1`] = `"- Awesome change (user1@example.com)"`;

exports[`changelog renderers - renderEntry has correct output 1`] = `"- Awesome change (user1@example.com)"`;

exports[`changelog renderers - renderHeader has correct grouped output 1`] = `
"## 1.2.3

Thu, 22 Aug 2019 21:20:40 GMT"
`;

exports[`changelog renderers - renderHeader has correct output 1`] = `
"## 1.2.3

Thu, 22 Aug 2019 21:20:40 GMT"
`;

exports[`changelog renderers - renderPackageChangelog has correct grouped output 1`] = `
"## 1.2.3

Expand All @@ -68,22 +20,6 @@ Thu, 22 Aug 2019 21:20:40 GMT
- stuff (user2@example.com)"
`;

exports[`changelog renderers - renderPackageChangelog has correct output 1`] = `
"## 1.2.3

Thu, 22 Aug 2019 21:20:40 GMT

### Minor changes

- Awesome change (user1@example.com)
- Boring change (user2@example.com)

### Patches

- Fix (user1@example.com)
- stuff (user2@example.com)"
`;

exports[`changelog renderers - renderPackageChangelog includes all change types 1`] = `
"## 1.2.3

Expand Down
107 changes: 86 additions & 21 deletions src/__tests__/changelog/renderPackageChangelog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { SortedChangeTypes } from '../../changefile/changeTypes';

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

const leadingNewlineRegex = /^\n/;
const trailingNewlineRegex = /\n$/;
const leadingTrailingNewlineRegex = /^\n|\n$/;

describe('changelog renderers -', () => {
function getRenderInfo(): PackageChangelogRenderInfo {
Expand All @@ -32,6 +31,7 @@ describe('changelog renderers -', () => {
},
previousJson: {} as ChangelogJson,
renderers: { ...defaultRenderers }, // copy in case of modification
defaultRenderers,
};
}

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

function doBasicTests(result: string) {
expect(result).not.toMatch(leadingNewlineRegex);
expect(result).not.toMatch(trailingNewlineRegex);
expect(result).toMatchSnapshot();
}

describe('renderEntry', () => {
it('has correct output', async () => {
const renderInfo = getRenderInfo();
const result = await renderEntry(renderInfo.newVersionChangelog.comments.minor![0], renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchInlineSnapshot(`"- Awesome change (user1@example.com)"`);
});

it('has correct grouped output', async () => {
const renderInfo = getGroupedRenderInfo();
const result = await renderEntry(renderInfo.newVersionChangelog.comments.minor![0], renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchInlineSnapshot(`"- Awesome change (user1@example.com)"`);
});

it('escapes < outside of code blocks', async () => {
const renderInfo = getRenderInfo();
renderInfo.newVersionChangelog.comments.minor![0].comment = 'Add --config <file>';
const result = await renderEntry(renderInfo.newVersionChangelog.comments.minor![0], renderInfo);
expect(result).toMatchInlineSnapshot(`"- Add --config \\<file> (user1@example.com)"`);
});

it('does not escape < inside code blocks', async () => {
const renderInfo = getRenderInfo();
renderInfo.newVersionChangelog.comments.minor![0].comment = 'Add `--config <file>`';
const result = await renderEntry(renderInfo.newVersionChangelog.comments.minor![0], renderInfo);
expect(result).toMatchInlineSnapshot(`"- Add \`--config <file>\` (user1@example.com)"`);
});
});

describe('renderEntries', () => {
it('has correct output', async () => {
const renderInfo = getRenderInfo();
const result = await renderEntries('minor', renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchInlineSnapshot(`
"- Awesome change (user1@example.com)
- Boring change (user2@example.com)"
`);
});

it('has correct grouped output', async () => {
const renderInfo = getGroupedRenderInfo();
const result = await renderEntries('minor', renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchInlineSnapshot(`
"- \`bar\`
- Awesome change (user1@example.com)
- \`foo\`
- Boring change (user2@example.com)"
`);
});
});

describe('renderChangeTypeHeader', () => {
it('has correct output', async () => {
const renderInfo = getRenderInfo();
const result = await renderChangeTypeHeader('minor', renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchInlineSnapshot(`"### Minor changes"`);
});

it('has correct grouped output', async () => {
const renderInfo = getGroupedRenderInfo();
const result = await renderChangeTypeHeader('minor', renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchInlineSnapshot(`"### Minor changes"`);
});
});

describe('renderChangeTypeSection', () => {
it('has correct output', async () => {
const renderInfo = getRenderInfo();
const result = await renderChangeTypeSection('minor', renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchInlineSnapshot(`
"### Minor changes

- Awesome change (user1@example.com)
- Boring change (user2@example.com)"
`);
});

it('has correct grouped output', async () => {
const renderInfo = getGroupedRenderInfo();
const result = await renderChangeTypeSection('minor', renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchInlineSnapshot(`
"### Minor changes

- \`bar\`
- Awesome change (user1@example.com)
- \`foo\`
- Boring change (user2@example.com)"
`);
});
});

describe('renderHeader', () => {
it('has correct output', async () => {
const renderInfo = getRenderInfo();
const result = await renderHeader(renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchInlineSnapshot(`
"## 1.2.3

Thu, 22 Aug 2019 21:20:40 GMT"
`);
});

it('has correct grouped output', async () => {
const renderInfo = getGroupedRenderInfo();
const result = await renderHeader(renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchInlineSnapshot(`
"## 1.2.3

Thu, 22 Aug 2019 21:20:40 GMT"
`);
});
});

describe('renderPackageChangelog', () => {
it('has correct output', async () => {
const renderInfo = getRenderInfo();
const result = await renderPackageChangelog(renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
// Most of the package output snapshots go in a snapshot file since they're longer,
// but include one inline to help as a sanity check.
expect(result).toMatchInlineSnapshot(`
"## 1.2.3

Thu, 22 Aug 2019 21:20:40 GMT

### Minor changes

- Awesome change (user1@example.com)
- Boring change (user2@example.com)

### Patches

- Fix (user1@example.com)
- stuff (user2@example.com)"
`);
});

it('includes all change types', async () => {
Expand All @@ -149,13 +212,15 @@ describe('changelog renderers -', () => {
expect(result).toContain(`${type} change`);
}
}
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchSnapshot();
});

it('has correct grouped output', async () => {
const renderInfo = getGroupedRenderInfo();
const result = await renderPackageChangelog(renderInfo);
doBasicTests(result);
expect(result).not.toMatch(leadingTrailingNewlineRegex);
expect(result).toMatchSnapshot();
});

it('uses custom renderEntry', async () => {
Expand Down
4 changes: 3 additions & 1 deletion src/changelog/renderChangelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { renderPackageChangelog, defaultRenderers } from './renderPackageChangel
import type { ChangelogOptions, PackageChangelogRenderInfo } from '../types/ChangelogOptions';
import type { PackageChangelog } from '../types/ChangeLog';

export interface MarkdownChangelogRenderOptions extends Omit<PackageChangelogRenderInfo, 'renderers'> {
export interface MarkdownChangelogRenderOptions
extends Omit<PackageChangelogRenderInfo, 'renderers' | 'defaultRenderers'> {
previousContent: string;
changelogOptions: ChangelogOptions;
}
Expand Down Expand Up @@ -56,6 +57,7 @@ export async function renderChangelog(renderOptions: MarkdownChangelogRenderOpti
...defaultRenderers,
...customRenderers,
},
defaultRenderers: { ...defaultRenderers },
};

const packageChangelog = await (customRenderPackageChangelog || renderPackageChangelog)(renderInfo);
Expand Down
13 changes: 10 additions & 3 deletions src/changelog/renderPackageChangelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,16 @@ async function _renderEntriesBasic(
}

function _renderEntry(entry: ChangelogEntry): string {
if (entry.author === 'beachball') {
return `- ${entry.comment}`;
let comment = `- ${entry.comment}`;

if (comment.includes('<') && !/`[^`]*?</.test(comment)) {
// If the comment includes a < which definitely isn't inside a code block, escape it.
// (Not bothering with full backtick matching since it's generally low-consequence if wrong.)
// Full HTML escaping will be handled by the final markdown renderer; we just want to prevent
// comment contents that were likely not intended as HTML from breaking rendering.
// e.g. "Add --config <file> option"
comment = comment.replace(/</g, '\\<');
Comment thread Dismissed
}

return `- ${entry.comment} (${entry.author})`;
return entry.author === 'beachball' ? comment : `${comment} (${entry.author})`;
}
5 changes: 5 additions & 0 deletions src/types/ChangelogOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ export interface PackageChangelogRenderInfo {
* Default renderers will be included in cases where a custom option wasn't provided.
*/
renderers: Required<ChangelogRenderers>;

/**
* Default renderers for each element of the changelog.
*/
defaultRenderers: Required<ChangelogRenderers>;
}

export interface ChangelogRenderers {
Expand Down