// Removed import const includes = require('lodash.includes'); import * as fs from 'fs'; // Setup const pr = danger.github.pr; const commits = danger.github.commits; const modified = danger.git.modified_files; const bodyAndTitle = (pr.body + pr.title).toLowerCase(); // Custom modifiers for people submitting PRs to be able to say "skip this" const trivialPR = bodyAndTitle.includes('trivial'); const acceptedNoTests = bodyAndTitle.includes('skip new tests'); const typescriptOnly = (file: string) => includes(file, '.ts'); const filesOnly = (file: string) => fs.existsSync(file) && fs.lstatSync(file).isFile(); // Custom subsets of known files const modifiedAppFiles = modified .filter(p => includes(p, 'src/')) .filter(p => filesOnly(p) && typescriptOnly(p)); const modifiedTestFiles = modified.filter(p => includes(p, 'test/')); // Takes a list of file paths, and converts it into clickable links const linkableFiles = paths => { const repoURL = danger.github.pr.head.repo.html_url; const ref = danger.github.pr.head.ref; const links = paths.map(path => { return createLink(`${repoURL}/blob/${ref}/${path}`, path); }); return toSentence(links); }; // ["1", "2", "3"] to "1, 2 and 3" const toSentence = (array: Array<string>): string => { if (array.length === 1) { return array[0]; } return array.slice(0, array.length - 1).join(', ') + ' and ' + array.pop(); }; // ("/href/thing", "name") to "<a href="/href/thing">name</a>" const createLink = (href: string, text: string): string => `<a href='${href}'>${text}</a>`; // Raise about missing code inside files const raiseIssueAboutPaths = (type: Function, paths: string[], codeToInclude: string) => { if (paths.length > 0) { const files = linkableFiles(paths); const strict = '<code>' + codeToInclude + '</code>'; type(`Please ensure that ${strict} is enabled on: ${files}`); } }; const authors = commits.map(x => x.author && x.author.login); const isBot = authors.some(x => ['greenkeeper', 'renovate'].indexOf(x) > -1); if (!isBot) { // Rules // When there are app-changes and it's not a PR marked as trivial, expect // there to be CHANGELOG changes. const changelogChanges = includes(modified, 'Changelog.md'); if (modifiedAppFiles.length > 0 && !trivialPR && !changelogChanges) { fail('No CHANGELOG added.'); } // No PR is too small to warrant a paragraph or two of summary if (pr.body.length === 0) { fail('Please add a description to your PR.'); } const hasAppChanges = modifiedAppFiles.length > 0; const hasTestChanges = modifiedTestFiles.length > 0; // Warn when there is a big PR const bigPRThreshold = 500; if (danger.github.pr.additions + danger.github.pr.deletions > bigPRThreshold) { warn(':exclamation: Big PR'); } // Warn if there are library changes, but not tests if (hasAppChanges && !hasTestChanges) { warn( "There are library changes, but not tests. That's OK as long as you're refactoring existing code", ); } // Be careful of leaving testing shortcuts in the codebase const onlyTestFiles = modifiedTestFiles.filter(x => { const content = fs.readFileSync(x).toString(); return ( content.includes('it.only') || content.includes('describe.only') || content.includes('fdescribe') || content.includes('fit(') ); }); raiseIssueAboutPaths(fail, onlyTestFiles, 'an `only` was left in the test'); }