You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
186 lines
6.5 KiB
186 lines
6.5 KiB
const {danger, warn} = require('danger') |
|
const fs = require('fs') |
|
const path = require('path') |
|
|
|
/** |
|
* Note: if you update the checks in this file, please also update the file ./docs/danger.md |
|
*/ |
|
|
|
// Useful to see what we got in danger object |
|
// warn(JSON.stringify(danger)) |
|
|
|
const pr = danger.github.pr |
|
const github = danger.github |
|
// User who has created the PR. |
|
const user = pr.user.login |
|
const modified = danger.git.modified_files |
|
const created = danger.git.created_files |
|
const editedFiles = [...modified, ...created] |
|
|
|
// Check that the PR has a description |
|
if (pr.body.length == 0) { |
|
warn("Please provide a description for this PR.") |
|
} |
|
|
|
// Warn when there is a big PR |
|
if (editedFiles.length > 50) { |
|
message("This pull request seems relatively large. Please consider splitting it into multiple smaller ones.") |
|
} |
|
|
|
// Request a correct title for each PR |
|
if (pr.title.endsWith("…")) { |
|
fail("Please provide a complete title that can be used as a changelog entry.") |
|
} |
|
|
|
// Request a `PR-` label for each PR |
|
if (pr.labels.filter((label) => label.name.startsWith("PR-")).length != 1) { |
|
fail("Please add a `PR-` label to categorise the changelog entry.") |
|
} |
|
|
|
// check that frozen classes have not been modified |
|
const frozenClasses = [ |
|
] |
|
|
|
frozenClasses.forEach(frozen => { |
|
if (editedFiles.some(file => file.endsWith(frozen))) { |
|
fail("Frozen class `" + frozen + "` has been modified. Please do not modify frozen class.") |
|
} |
|
} |
|
) |
|
|
|
// Check for a sign-off |
|
const signOff = "Signed-off-by:" |
|
|
|
// Please add new names following the alphabetical order. |
|
const allowList = [ |
|
"aringenbach", |
|
"BillCarsonFr", |
|
"bmarty", |
|
"csmith", |
|
"dependabot[bot]", |
|
"Florian14", |
|
"ganfra", |
|
"github-actions[bot]", |
|
"jmartinesp", |
|
"jonnyandrew", |
|
"julioromano", |
|
"kittykat", |
|
"langleyd", |
|
"MadLittleMods", |
|
"manuroe", |
|
"renovate[bot]", |
|
"stefanceriu", |
|
"yostyle", |
|
] |
|
|
|
function signoff_needed(reason) { |
|
message("Sign-off required, " + reason) |
|
const hasPRBodySignOff = pr.body.includes(signOff) |
|
const hasCommitSignOff = danger.git.commits.every(commit => commit.message.includes(signOff)) |
|
if (!hasPRBodySignOff && !hasCommitSignOff) { |
|
fail("Please add a sign-off to either the PR description or to the commits themselves. See instructions [here](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off).") |
|
} |
|
} |
|
|
|
function signoff_unneeded(reason) { |
|
message("Sign-off not required, " + reason) |
|
} |
|
|
|
// Somewhat awkward phrasing, dangerfile is not in an async context. |
|
if (allowList.includes(user)) { |
|
signoff_unneeded("allow-list") |
|
} else { |
|
// github.api.rest.orgs.checkMembershipForUser({ |
|
// org: "element-hq", |
|
// username: user, |
|
// }).then((result) => { |
|
github.api.rest.teams.getMembershipForUserInOrg({ |
|
org: "element-hq", |
|
team_slug: "vector-core", |
|
username: user, |
|
}).then((result) => { |
|
if (result.status == 204 || result.status == 200) { |
|
signoff_unneeded("team-member") |
|
} |
|
else { |
|
signoff_needed("not-team-member") |
|
} |
|
}).catch((error) => { |
|
if (error.response.status == 404) { |
|
signoff_needed("not-team-member"); |
|
} else { |
|
console.log(error); signoff_needed("error") |
|
} |
|
}) |
|
} |
|
|
|
const previewAnnotations = [ |
|
'androidx.compose.ui.tooling.preview.Preview', |
|
'io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight', |
|
'io.element.android.libraries.designsystem.preview.PreviewsDayNight' |
|
] |
|
|
|
const filesWithPreviews = editedFiles.filter(file => file.endsWith(".kt")).filter(file => { |
|
const content = fs.readFileSync(file); |
|
return previewAnnotations.some((ann) => content.includes("import " + ann)); |
|
}) |
|
|
|
const composablePreviewProviderContents = fs.readFileSync('tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt'); |
|
const packageTreesRegex = /private val PACKAGE_TREES = arrayOf\(([\w\W]+?)\n\)/gm; |
|
const packageTreesMatch = packageTreesRegex.exec(composablePreviewProviderContents)[1]; |
|
const scannedPreviewPackageTrees = packageTreesMatch |
|
.replaceAll("\"", "") |
|
.replaceAll(",", "") |
|
.split('\n').map((line) => line.trim()) |
|
.filter((line) => line.length > 0); |
|
|
|
const previewPackagesNotIncludedInScreenshotTests = filesWithPreviews.map((file) => { |
|
const content = fs.readFileSync(file); |
|
const packageRegex = /package\s+([a-zA-Z0-9.]+)/; |
|
const packageMatch = packageRegex.exec(content); |
|
|
|
if (!packageMatch || packageMatch.length != 2) { |
|
return null; |
|
} |
|
|
|
return packageMatch[1]; |
|
|
|
|
|
}).filter((package) => { |
|
if (!package) { |
|
return false; |
|
} |
|
if (!scannedPreviewPackageTrees.some((prefix) => package.includes(prefix))) { |
|
return true; |
|
} |
|
}); |
|
|
|
if (previewPackagesNotIncludedInScreenshotTests.length > 0) { |
|
const packagesList = previewPackagesNotIncludedInScreenshotTests.map((p) => '- `' + p + '`').join("\n"); |
|
warn("You have made changes to a file containing a `@Preview` annotated function but its package name prefix is not included in the `ComposablePreviewProvider`.\nPackages missing in `tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt`: \n" + packagesList); |
|
} |
|
|
|
// Check for pngs on resources |
|
const hasPngs = editedFiles.filter(file => { |
|
file.toLowerCase().endsWith(".png") && !file.includes("snapshots/images/") // Exclude screenshots |
|
}).length > 0 |
|
if (hasPngs) { |
|
warn("You seem to have made changes to some images. Please consider using an vector drawable.") |
|
} |
|
|
|
// Check that translations have not been modified by developers |
|
const translationAllowList = [ |
|
"RiotTranslateBot", |
|
"github-actions[bot]", |
|
] |
|
|
|
if (!translationAllowList.includes(user)) { |
|
if (editedFiles.some(file => file.endsWith("strings.xml") && !file.endsWith("values/strings.xml"))) { |
|
fail("Some translation files have been edited. Only user `RiotTranslateBot` (i.e. translations coming from Weblate) or `github-actions[bot]` (i.e. translations coming from automation) are allowed to do that.\nPlease read more about translations management [in the doc](https://github.com/element-hq/element-android/blob/develop/CONTRIBUTING.md#internationalisation).") |
|
} |
|
|
|
// Check that new strings are not added to `values/strings.xml` |
|
if (editedFiles.some(file => file.endsWith("ui-strings/src/main/res/values/strings.xml"))) { |
|
fail("`ui-strings/src/main/res/values/strings.xml` has been edited. This file will be overridden in the next strings synchronisation. Please add new strings in the file `values/strings_eax.xml` instead.") |
|
} |
|
}
|
|
|