Skip to content

Commit a6dde1e

Browse files
authored
chore(prlint): add exempt label to breaking change on stable modules (#18102)
This PR introduces a proposed new label, `pr-linter/exempt-breaking-change` that, when added, circumvents the check that asserts stable modules do not have breaking changes. Motivation: A situation like #18027 where we have are willing to accept a functional breaking change to a stable module. The regular `allowed-breaking-changes.txt` file does not work here, since there is no breaking change to the API. We want to be able to document the breaking change, but by documenting we alert `prlint` that we are breaking a stable module. Counterargument: Functional breaking changes were explicitly banned in #14861. From the PR description: "The CDK must be more strict on preventing such changes and the impact due to their perception." I also added some "manual linting" to the file myself since it was bothering me, and now it muddies the diff. Sorry! ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 32f1c80 commit a6dde1e

File tree

2 files changed

+41
-20
lines changed

2 files changed

+41
-20
lines changed

tools/@aws-cdk/prlint/lint.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import * as GitHub from 'github-api';
33
import { breakingModules } from './parser';
44
import { findModulePath, moduleStability } from './module';
55

6-
const OWNER = "aws"
7-
const REPO = "aws-cdk"
8-
const EXEMPT_README = 'pr-linter/exempt-readme'
9-
const EXEMPT_TEST = 'pr-linter/exempt-test'
6+
const OWNER = 'aws';
7+
const REPO = 'aws-cdk';
8+
const EXEMPT_README = 'pr-linter/exempt-readme';
9+
const EXEMPT_TEST = 'pr-linter/exempt-test';
10+
const EXEMPT_BREAKING_CHANGE = 'pr-linter/exempt-breaking-change';
1011

1112
class LinterError extends Error {
1213
constructor(message: string) {
@@ -18,9 +19,9 @@ function createGitHubClient() {
1819
const token = process.env.GITHUB_TOKEN;
1920

2021
if (token) {
21-
console.log("Creating authenticated GitHub Client")
22+
console.log("Creating authenticated GitHub Client");
2223
} else {
23-
console.log("Creating un-authenticated GitHub Client")
24+
console.log("Creating un-authenticated GitHub Client");
2425
}
2526

2627
return new GitHub({'token': token});
@@ -49,19 +50,19 @@ function readmeChanged(files: any[]) {
4950
function featureContainsReadme(issue: any, files: any[]) {
5051
if (isFeature(issue) && !readmeChanged(files) && !isPkgCfnspec(issue)) {
5152
throw new LinterError("Features must contain a change to a README file");
52-
};
53+
}
5354
};
5455

5556
function featureContainsTest(issue: any, files: any[]) {
5657
if (isFeature(issue) && !testChanged(files)) {
5758
throw new LinterError("Features must contain a change to a test file");
58-
};
59+
}
5960
};
6061

6162
function fixContainsTest(issue: any, files: any[]) {
6263
if (isFix(issue) && !testChanged(files)) {
6364
throw new LinterError("Fixes must contain a change to a test file");
64-
};
65+
}
6566
};
6667

6768
function shouldExemptReadme(issue: any) {
@@ -72,6 +73,10 @@ function shouldExemptTest(issue: any) {
7273
return hasLabel(issue, EXEMPT_TEST);
7374
}
7475

76+
function shouldExemptBreakingChange(issue: any) {
77+
return hasLabel(issue, EXEMPT_BREAKING_CHANGE);
78+
}
79+
7580
function hasLabel(issue: any, labelName: string) {
7681
return issue.labels.some(function (l: any) {
7782
return l.name === labelName;
@@ -93,7 +98,7 @@ function validateBreakingChangeFormat(title: string, body: string) {
9398
throw new LinterError(`Breaking changes should be indicated by starting a line with 'BREAKING CHANGE: ', variations are not allowed. (found: '${m[0]}')`);
9499
}
95100
if (m[0].substr('BREAKING CHANGE:'.length).trim().length === 0) {
96-
throw new LinterError("The description of the first breaking change should immediately follow the 'BREAKING CHANGE: ' clause")
101+
throw new LinterError("The description of the first breaking change should immediately follow the 'BREAKING CHANGE: ' clause");
97102
}
98103
const titleRe = /^[a-z]+\([0-9a-z-_]+\)/;
99104
if (!titleRe.exec(title)) {
@@ -104,7 +109,7 @@ function validateBreakingChangeFormat(title: string, body: string) {
104109

105110
function assertStability(title: string, body: string) {
106111
const breakingStable = breakingModules(title, body)
107-
.filter(mod => 'stable' === moduleStability(findModulePath(mod)))
112+
.filter(mod => 'stable' === moduleStability(findModulePath(mod)));
108113

109114
if (breakingStable.length > 0) {
110115
throw new Error(`Breaking changes in stable modules [${breakingStable.join(', ')}] is disallowed.`);
@@ -114,40 +119,43 @@ function assertStability(title: string, body: string) {
114119
export async function validatePr(number: number) {
115120

116121
if (!number) {
117-
throw new Error('Must provide a PR number')
122+
throw new Error('Must provide a PR number');
118123
}
119124

120125
const gh = createGitHubClient();
121126

122127
const issues = gh.getIssues(OWNER, REPO);
123128
const repo = gh.getRepo(OWNER, REPO);
124129

125-
console.log(`⌛ Fetching PR number ${number}`)
130+
console.log(`⌛ Fetching PR number ${number}`);
126131
const issue = (await issues.getIssue(number)).data;
127132

128-
console.log(`⌛ Fetching files for PR number ${number}`)
133+
console.log(`⌛ Fetching files for PR number ${number}`);
129134
const files = (await repo.listPullRequestFiles(number)).data;
130135

131136
console.log("⌛ Validating...");
132137

133138
if (shouldExemptReadme(issue)) {
134-
console.log(`Not validating README changes since the PR is labeled with '${EXEMPT_README}'`)
139+
console.log(`Not validating README changes since the PR is labeled with '${EXEMPT_README}'`);
135140
} else {
136141
featureContainsReadme(issue, files);
137142
}
138143

139144
if (shouldExemptTest(issue)) {
140-
console.log(`Not validating test changes since the PR is labeled with '${EXEMPT_TEST}'`)
145+
console.log(`Not validating test changes since the PR is labeled with '${EXEMPT_TEST}'`);
141146
} else {
142147
featureContainsTest(issue, files);
143148
fixContainsTest(issue, files);
144149
}
145-
150+
146151
validateBreakingChangeFormat(issue.title, issue.body);
152+
if (shouldExemptBreakingChange(issue)) {
153+
console.log(`Not validating breaking changes since the PR is labeled with '${EXEMPT_BREAKING_CHANGE}'`);
154+
} else {
155+
assertStability(issue.title, issue.body);
156+
}
147157

148-
assertStability(issue.title, issue.body)
149-
150-
console.log("✅ Success")
158+
console.log("✅ Success");
151159

152160
}
153161

tools/@aws-cdk/prlint/test/lint.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@ describe('ban breaking changes in stable modules', () => {
8282
await expect(linter.validatePr(1000)).rejects.toThrow('Breaking changes in stable modules [lambda, ecs] is disallowed.');
8383
});
8484

85+
test('unless exempt-breaking-change label added', async () => {
86+
const issue = {
87+
title: 'chore(lambda): some title',
88+
body: `
89+
BREAKING CHANGE: this breaking change
90+
continued message
91+
`,
92+
labels: [{ name: 'pr-linter/exempt-breaking-change' }],
93+
};
94+
configureMock(issue, undefined);
95+
await expect(linter.validatePr(1000)).resolves; // not throw
96+
});
97+
8598
test('with additional "closes" footer', async () => {
8699
const issue = {
87100
title: 'chore(s3): some title',

0 commit comments

Comments
 (0)