Files
Emily Brown 5e41043fb5 Add PR body validation to analyze-pr workflow (#55382)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/55382

This adds a `validatePRBody.js` script that checks pull request descriptions for required sections as part of a new `analyze-pr.yml` workflow that will replace part of the Danger-pr workflow.

The validation includes:
- **Description length check**: Fails if the PR body is missing or less than 50 characters
- **Summary section check**: Warns if the PR lacks a "## Summary" section
- **Test plan check**: Warns if the PR lacks a "## Test Plan" section
- **Changelog validation**: failing if missing or invalid

Key behaviors:
- Phabricator-sourced PRs (detected via "Differential Revision:" in body) skip summary, test plan, and changelog validation since these are enforced differently
- Validation messages use GitHub's `[!WARNING]` and `[!CAUTION]` callout syntax for clear visual feedback
- The workflow fails (via `core.setFailed`) when any required check fails
- Messages are passed to `postPRComment` alongside API changes for consolidated PR comments

This aims to mimic relevant parts of dangerfile.js

Changelog: [Internal]

Reviewed By: huntie

Differential Revision: D91158803

fbshipit-source-id: 2304251d18f9fc8bf9a29e536fff2d979573bd86
2026-02-06 08:14:53 -08:00

103 lines
2.7 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
const changelogRegex =
/\[\s?(ANDROID|GENERAL|IOS|INTERNAL)\s?\]\s?\[\s?(BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY)\s?\]\s*?-?\s*?(.*)/i;
const internalChangelogRegex = /\[\s?(INTERNAL)\s?\].*/gi;
function validateChangelog(commitMsg) {
if (!commitMsg.toLowerCase().includes('changelog:')) {
return 'missing';
}
const hasValidChangelog = changelogRegex.test(commitMsg);
const hasValidInternalChangelog = internalChangelogRegex.test(commitMsg);
if (hasValidChangelog || hasValidInternalChangelog) {
return 'valid';
}
return 'invalid';
}
function validatePRBody(prBody) {
const body = prBody ?? '';
const bodyLower = body.toLowerCase();
const isFromPhabricator = bodyLower.includes('differential revision:');
const messages = [];
let hasFail = false;
function addWarning(title, text) {
messages.push(`> [!WARNING]
> **${title}**
>
> ${text}`);
}
function addFail(title, text) {
hasFail = true;
messages.push(`> [!CAUTION]
> **${title}**
>
> ${text}`);
}
if (!body || body.length < 50) {
addFail('Missing Description', 'This pull request needs a description.');
} else {
const hasSummary =
bodyLower.includes('## summary') || bodyLower.includes('summary:');
if (!hasSummary && body.split('\n').length <= 2 && !isFromPhabricator) {
addWarning(
'Missing Summary',
'Please add a "## Summary" section to your PR description. This is a good place to explain the motivation for making this change.',
);
}
}
if (!isFromPhabricator) {
const hasTestPlan = ['## test plan', 'test plan:', 'tests:', 'test:'].some(
t => bodyLower.includes(t),
);
if (!hasTestPlan) {
addWarning(
'Missing Test Plan',
'Please add a "## Test Plan" section to your PR description. A Test Plan lets us know how these changes were tested.',
);
}
}
if (!isFromPhabricator) {
const status = validateChangelog(body);
const link =
'https://reactnative.dev/contributing/changelogs-in-pull-requests';
if (status === 'missing') {
addFail(
'Missing Changelog',
`Please add a Changelog to your PR description. See [Changelog format](${link})`,
);
} else if (status === 'invalid') {
addFail(
'Invalid Changelog Format',
`Please verify your Changelog format. See [Changelog format](${link})`,
);
}
}
return {
message: messages.join('\n\n'),
status: hasFail ? 'FAIL' : 'PASS',
};
}
module.exports = validatePRBody;