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 changelog for each PR
const changelogAllowList = [
"dependabot[bot]" ,
]
const requiresChangelog = ! changelogAllowList . includes ( user )
if ( requiresChangelog ) {
const changelogFiles = editedFiles . filter ( file => file . startsWith ( "changelog.d/" ) )
if ( changelogFiles . length == 0 ) {
warn ( "Please add a changelog. See instructions [here](https://github.com/element-hq/element-android/blob/develop/CONTRIBUTING.md#changelog)" )
} else {
const validTowncrierExtensions = [
"bugfix" ,
"doc" ,
"feature" ,
"misc" ,
"wip" ,
]
if ( ! changelogFiles . every ( file => validTowncrierExtensions . includes ( file . split ( "." ) . pop ( ) ) ) ) {
fail ( "Invalid extension for changelog. See instructions [here](https://github.com/element-hq/element-android/blob/develop/CONTRIBUTING.md#changelog)" )
}
}
}
// 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 ( ann ) ) ;
} )
const buildFilesWithMissingProcessor = filesWithPreviews . map ( file => {
let parent = path . dirname ( file ) ;
while ( fs . statSync ( path . join ( parent , 'build.gradle.kts' ) , { throwIfNoEntry : false } ) === undefined ) {
parent = path . dirname ( parent ) ;
}
return path . join ( parent , 'build.gradle.kts' ) ;
} ) . filter ( ( value , index , array ) => array . indexOf ( value ) === index ) . filter ( buildFile => {
const content = fs . readFileSync ( buildFile ) ;
return ! content . includes ( 'ksp(libs.showkase.processor)' ) ;
} )
if ( buildFilesWithMissingProcessor . length > 0 ) {
warn ( "You have made changes to a file containing a `@Preview` annotated function but its module doesn't include the showkase processor. Missing processor in: " + buildFilesWithMissingProcessor . join ( ", " ) )
}
// 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." )
}
}