Last active
April 12, 2019 04:29
-
-
Save tw3/7d710da95fb13ac1d4cfc41b965dd62e to your computer and use it in GitHub Desktop.
PR blocker script that returns outputs ESLint messages for lines that have been modified vs git master branch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const Git = require('nodegit'); | |
| const CLIEngine = require('eslint').CLIEngine; | |
| /** | |
| * Usage: node lint_modified_lines.js | |
| * | |
| * This script: | |
| * 1. Determines the files and lines that have been added or changed compared to the master branch. | |
| * 2. Runs eslint. | |
| * 3. Filters the lint results to only the lines that have been added or changed. | |
| */ | |
| const DEBUG = true; | |
| const REPO_PATH = './'; | |
| async function main() { | |
| // Get mapping from the filePath -> modified lines for each file | |
| const modifiedFileLines = await getModifiedFileLines(REPO_PATH); | |
| try { | |
| const report = await getEslintReport(modifiedFileLines); | |
| console.log('SUCCESS'); | |
| console.log(report); | |
| process.exit(0); // success | |
| } catch (err) { | |
| console.log('ERROR'); | |
| // The promise in getEslintReport() will reject() when linting fails | |
| console.error(err); | |
| process.exit(1); // failure | |
| } | |
| } | |
| // -------------------------------- | |
| // getModifiedFileLines and helpers | |
| // -------------------------------- | |
| async function getModifiedFileLines(repoPath) { | |
| const modifiedFileLines = new Map(); | |
| const patches = await getDiffPatches(repoPath); | |
| for (const patch of patches) { | |
| const filePath = getFilePathFromPatch(patch); | |
| if (!isLintableFileOrPatch(filePath, patch)) { | |
| continue; | |
| } | |
| const modifiedLinesInFile = await getModifiedLinesFromPatch(patch); | |
| if (modifiedLinesInFile.length > 0) { | |
| modifiedFileLines.set(filePath, modifiedLinesInFile); | |
| } | |
| } | |
| return modifiedFileLines; | |
| } | |
| async function getDiffPatches(repoPath) { | |
| const repo = await Git.Repository.open(repoPath); | |
| const head = await repo.getBranchCommit('master'); | |
| const emptyTree = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'; | |
| const tree = await (head ? head.getTree() : Git.Tree.lookup(repo, emptyTree)); | |
| const diff = await Git.Diff.treeToWorkdir(repo, tree, { | |
| flags: Git.Diff.OPTION.SHOW_UNTRACKED_CONTENT | Git.Diff.OPTION.RECURSE_UNTRACKED_DIRS, | |
| contextLines: 0 | |
| }); | |
| const patches = await diff.patches(); | |
| return patches; | |
| } | |
| function getFilePathFromPatch(patch) { | |
| let filePath; | |
| const unresolvedFilePath = patch.newFile().path(); | |
| if (DEBUG) console.log('\nfilePath:', unresolvedFilePath); | |
| try { | |
| filePath = require.resolve(`../${unresolvedFilePath}`); | |
| } catch (err) { | |
| console.warn('WARNING: Could not resolve file (skipping)'); | |
| } | |
| // if (DEBUG) console.log('resolved filePath:', filePath); | |
| return filePath; | |
| } | |
| function isLintableFileOrPatch(filePath, patch) { | |
| if (!filePath) { | |
| return false; | |
| } | |
| const isLintableFile = (filePath.endsWith('.js') || filePath.endsWith('.ts') || filePath.endsWith('.json')); | |
| if (!isLintableFile) { | |
| if (DEBUG) console.log('File is not lintable based on the file path'); | |
| return false; | |
| } | |
| if (patch.isDeleted()) { | |
| if (DEBUG) console.log('File is not lintable since it is deleted'); | |
| return false; | |
| } | |
| return true; | |
| } | |
| async function getModifiedLinesFromPatch(patch) { | |
| const hunks = await patch.hunks(); | |
| const modifiedLines = []; | |
| for (const hunk of hunks) { | |
| const startLineNum = hunk.newStart(); | |
| const numNewLines = hunk.newLines(); | |
| if (numNewLines > 0) { | |
| const endLineNum = startLineNum + numNewLines - 1; | |
| const modifiedLineObj = { startLineNum, endLineNum }; | |
| if (DEBUG) console.log('modifiedLineObj', modifiedLineObj); | |
| modifiedLines.push(modifiedLineObj); | |
| } | |
| } | |
| return modifiedLines; | |
| } | |
| // --------------------------- | |
| // getEslintReport and helpers | |
| // --------------------------- | |
| function getEslintReport(modifiedFileLines) { | |
| const config = { configFile: require.resolve('../.eslintrc.js') }; | |
| const cli = new CLIEngine({ | |
| ...config | |
| }); | |
| const filePaths = Array.from(modifiedFileLines.keys()); | |
| return new Promise((resolve, reject) => { | |
| const report = cli.executeOnFiles(filePaths); | |
| const errorResults = CLIEngine.getErrorResults(report.results); | |
| const modifiedErrorResults = []; | |
| let hasError = false; | |
| // Construct the array of modified error results | |
| for (const errorResult of errorResults) { | |
| const modifiedLinesInFile = modifiedFileLines.get(errorResult.filePath); | |
| const modifiedErrorResult = getModifiedErrorResult(errorResult, modifiedLinesInFile); | |
| if (modifiedErrorResult) { | |
| hasError = (modifiedErrorResult.errorCount > 0); | |
| modifiedErrorResults.push(modifiedErrorResult); | |
| } | |
| } | |
| // Resolve or reject with formatted output | |
| const formatter = cli.getFormatter('stylish'); // another format option: 'codeframe' | |
| if (hasError) { | |
| reject(formatter(modifiedErrorResults)); | |
| } else { | |
| resolve(formatter(modifiedErrorResults)); | |
| } | |
| }); | |
| } | |
| function getModifiedErrorResult(errorResult, modifiedLines) { | |
| if (!errorResult.messages) { | |
| return undefined; | |
| } | |
| let counts = { | |
| error: { | |
| fixable: 0, | |
| total: 0 | |
| }, | |
| warn: { | |
| fixable: 0, | |
| total: 0 | |
| } | |
| }; | |
| // Filter down the messages to the lines that were modified (and tally counts) | |
| const modifiedLineMessages = errorResult.messages.filter(message => { | |
| const msgLineNum = message.line; | |
| const isLineNumModified = modifiedLines.some(modifiedLineObj => { | |
| return ( | |
| msgLineNum >= modifiedLineObj.startLineNum && | |
| msgLineNum <= modifiedLineObj.endLineNum | |
| ); | |
| }); | |
| // Tally counts along the way | |
| if (isLineNumModified) { | |
| // console.log({message}); | |
| const obj = (message.severity === 1) ? counts.warn : | |
| (message.severity === 2) ? counts.error : | |
| {}; | |
| obj.total++; | |
| if (message.hasOwnProperty('fix')) { | |
| obj.fixable++; | |
| } | |
| } | |
| return isLineNumModified; | |
| }); | |
| if (modifiedLineMessages.length === 0) { | |
| return undefined; | |
| } | |
| // console.log({errorResult}); | |
| return { | |
| ...errorResult, | |
| errorCount: counts.error.total, | |
| fixableErrorCount: counts.error.fixable, | |
| warningCount: counts.warn.total, | |
| fixableWarningCount: counts.warn.fixable, | |
| messages: modifiedLineMessages | |
| }; | |
| } | |
| main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment