mirror of
https://github.com/facebook/react-native.git
synced 2024-11-22 06:29:46 +00:00
767f8e0249
Summary: allow-large-files When working on https://github.com/facebook/react-native/pull/34614, danger is failing because it doesn't share `node_modules` with the root directory where `typescript` is installed as we added it as a parser in our eslint config. By setting `bots` as a yarn workspace, dependencies are all installed under the root `node_modules` folder and in local testing (detailed in test section) we no longer have the `typescript module not found` error. However, danger will continue to fail on https://github.com/facebook/react-native/pull/34614 as the `danger_pr` Github action runs from what's defined on `main`. Once these changes land, I can rebase https://github.com/facebook/react-native/pull/34614 on it and danger's eslint should pass. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> [Internal][Fixed] - Add `bots` directory as a yarn workspace and update `danger_pr` Github action Pull Request resolved: https://github.com/facebook/react-native/pull/34652 Test Plan: To verify this fix I had to run: ``` react-native $ yarn && cd bots react-native/bots$ yarn run danger pr https://github.com/facebook/react-native/pull/34614 ``` which resulted in ``` ❯ yarn run danger pr https://github.com/facebook/react-native/pull/34614 yarn run v1.22.19 $ lunaleaps/react-native/node_modules/.bin/danger pr https://github.com/facebook/react-native/pull/34614 Starting Danger PR on facebook/react-native#34614 Danger: ✓ found only warnings, not failing the build ## Warnings 🔒 package.json - <i>Changes were made to package.json. This will require a manual import by a Facebook employee.</i> ✨ Done in 12.78s. ``` Verified this also on another PR: ``` yarn run danger pr https://github.com/facebook/react-native/pull/34650 ``` Reviewed By: NickGerleman Differential Revision: D39435286 Pulled By: lunaleaps fbshipit-source-id: 8c82f49facf162f4fc0918e3abd95eb7e4ad1e37
351 lines
8.8 KiB
JavaScript
351 lines
8.8 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';
|
|
|
|
if (!process.env.GITHUB_OWNER) {
|
|
console.error('Missing GITHUB_OWNER. Example: facebook');
|
|
process.exit(1);
|
|
}
|
|
if (!process.env.GITHUB_REPO) {
|
|
console.error('Missing GITHUB_REPO. Example: react-native');
|
|
process.exit(1);
|
|
}
|
|
|
|
const path = require('path');
|
|
|
|
function push(arr, key, value) {
|
|
if (!arr[key]) {
|
|
arr[key] = [];
|
|
}
|
|
arr[key].push(value);
|
|
}
|
|
|
|
const converterSummary = {
|
|
eslint:
|
|
'`eslint` found some issues. Run `yarn lint --fix` to automatically fix problems.',
|
|
flow: '`flow` found some issues. Run `yarn flow check` to analyze your code and address any errors.',
|
|
shellcheck:
|
|
'`shellcheck` found some issues. Run `yarn shellcheck` to analyze shell scripts.',
|
|
'google-java-format':
|
|
'`google-java-format` found some issues. See https://github.com/google/google-java-format',
|
|
};
|
|
|
|
/**
|
|
* There is unfortunately no standard format to report an error, so we have
|
|
* to write a specific converter for each tool we want to support.
|
|
*
|
|
* Those functions take a json object as input and fill the output with the
|
|
* following format:
|
|
*
|
|
* { [ path: string ]: Array< { message: string, line: number }> }
|
|
*
|
|
* This is an object where the keys are the path of the files and values
|
|
* is an array of objects of the shape message and line.
|
|
*/
|
|
const converters = {
|
|
raw: function (output, input) {
|
|
for (let key in input) {
|
|
input[key].forEach(function (message) {
|
|
push(output, key, message);
|
|
});
|
|
}
|
|
},
|
|
|
|
'google-java-format': function (output, input) {
|
|
if (!input) {
|
|
return;
|
|
}
|
|
|
|
input.forEach(function (change) {
|
|
push(output, change.file, {
|
|
message: `\`google-java-format\` suggested changes:
|
|
\`\`\`diff
|
|
${change.description}
|
|
\`\`\`
|
|
`,
|
|
line: change.line,
|
|
converter: 'google-java-format',
|
|
});
|
|
});
|
|
},
|
|
|
|
flow: function (output, input) {
|
|
if (!input || !input.errors) {
|
|
return;
|
|
}
|
|
|
|
input.errors.forEach(function (error) {
|
|
push(output, error.message[0].path, {
|
|
message: error.message.map(message => message.descr).join(' '),
|
|
line: error.message[0].line,
|
|
converter: 'flow',
|
|
});
|
|
});
|
|
},
|
|
|
|
eslint: function (output, input) {
|
|
if (!input) {
|
|
return;
|
|
}
|
|
|
|
input.forEach(function (file) {
|
|
file.messages.forEach(function (message) {
|
|
push(output, file.filePath, {
|
|
message: message.ruleId + ': ' + message.message,
|
|
line: message.line,
|
|
converter: 'eslint',
|
|
});
|
|
});
|
|
});
|
|
},
|
|
|
|
shellcheck: function (output, input) {
|
|
if (!input) {
|
|
return;
|
|
}
|
|
|
|
input.forEach(function (report) {
|
|
push(output, report.file, {
|
|
message:
|
|
'**[SC' +
|
|
report.code +
|
|
'](https://github.com/koalaman/shellcheck/wiki/SC' +
|
|
report.code +
|
|
'):** (' +
|
|
report.level +
|
|
') ' +
|
|
report.message,
|
|
line: report.line,
|
|
endLine: report.endLine,
|
|
column: report.column,
|
|
endColumn: report.endColumn,
|
|
converter: 'shellcheck',
|
|
});
|
|
});
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Sadly we can't just give the line number to github, we have to give the
|
|
* line number relative to the patch file which is super annoying. This
|
|
* little function builds a map of line number in the file to line number
|
|
* in the patch file
|
|
*/
|
|
function getLineMapFromPatch(patchString) {
|
|
let diffLineIndex = 0;
|
|
let fileLineIndex = 0;
|
|
let lineMap = {};
|
|
|
|
patchString.split('\n').forEach(line => {
|
|
if (line.match(/^@@/)) {
|
|
fileLineIndex = line.match(/\+([0-9]+)/)[1] - 1;
|
|
return;
|
|
}
|
|
|
|
diffLineIndex++;
|
|
if (line[0] !== '-') {
|
|
fileLineIndex++;
|
|
if (line[0] === '+') {
|
|
lineMap[fileLineIndex] = diffLineIndex;
|
|
}
|
|
}
|
|
});
|
|
|
|
return lineMap;
|
|
}
|
|
|
|
async function sendReview(
|
|
octokit,
|
|
owner,
|
|
repo,
|
|
pull_number,
|
|
commit_id,
|
|
body,
|
|
comments,
|
|
) {
|
|
if (process.env.GITHUB_TOKEN) {
|
|
if (comments.length === 0) {
|
|
// Do not leave an empty review.
|
|
return;
|
|
} else if (comments.length > 5) {
|
|
// Avoid noisy reviews and rely solely on the body of the review.
|
|
comments = [];
|
|
}
|
|
|
|
const event = 'REQUEST_CHANGES';
|
|
|
|
const opts = {
|
|
owner,
|
|
repo,
|
|
pull_number,
|
|
commit_id,
|
|
body,
|
|
event,
|
|
comments,
|
|
};
|
|
|
|
await octokit.pulls.createReview(opts);
|
|
} else {
|
|
if (comments.length === 0) {
|
|
console.log('No issues found.');
|
|
return;
|
|
}
|
|
|
|
if (process.env.CIRCLE_CI) {
|
|
console.error(
|
|
'Code analysis found issues, but the review cannot be posted to GitHub without an access token.',
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
let results = body + '\n';
|
|
comments.forEach(comment => {
|
|
results +=
|
|
comment.path + ':' + comment.position + ': ' + comment.body + '\n';
|
|
});
|
|
console.log(results);
|
|
}
|
|
}
|
|
|
|
async function main(messages, owner, repo, pull_number) {
|
|
// No message, we don't need to do anything :)
|
|
if (Object.keys(messages).length === 0) {
|
|
return;
|
|
}
|
|
|
|
if (!process.env.GITHUB_TOKEN) {
|
|
console.log(
|
|
'Missing GITHUB_TOKEN. Example: 5fd88b964fa214c4be2b144dc5af5d486a2f8c1e. Review feedback with code analysis results will not be provided on GitHub without a valid token.',
|
|
);
|
|
}
|
|
|
|
// https://octokit.github.io/rest.js/
|
|
const {Octokit} = require('@octokit/rest');
|
|
const octokit = new Octokit({
|
|
auth: process.env.GITHUB_TOKEN,
|
|
userAgent: 'react-native-code-analysis-bot',
|
|
});
|
|
|
|
const opts = {
|
|
owner,
|
|
repo,
|
|
pull_number,
|
|
};
|
|
|
|
const {data: pull} = await octokit.pulls.get(opts);
|
|
const {data: files} = await octokit.pulls.listFiles(opts);
|
|
|
|
const comments = [];
|
|
const convertersUsed = [];
|
|
|
|
files
|
|
.filter(file => messages[file.filename])
|
|
.forEach(file => {
|
|
// github api sometimes does not return a patch on large commits
|
|
if (!file.patch) {
|
|
return;
|
|
}
|
|
const lineMap = getLineMapFromPatch(file.patch);
|
|
messages[file.filename].forEach(message => {
|
|
if (lineMap[message.line]) {
|
|
const comment = {
|
|
path: file.filename,
|
|
position: lineMap[message.line],
|
|
body: message.message,
|
|
};
|
|
convertersUsed.push(message.converter);
|
|
comments.push(comment);
|
|
}
|
|
}); // forEach
|
|
}); // filter
|
|
|
|
let body = '**Code analysis results:**\n\n';
|
|
const uniqueconvertersUsed = [...new Set(convertersUsed)];
|
|
uniqueconvertersUsed.forEach(converter => {
|
|
body += '* ' + converterSummary[converter] + '\n';
|
|
});
|
|
|
|
await sendReview(
|
|
octokit,
|
|
owner,
|
|
repo,
|
|
pull_number,
|
|
pull.head.sha,
|
|
body,
|
|
comments,
|
|
);
|
|
}
|
|
|
|
let content = '';
|
|
process.stdin.resume();
|
|
process.stdin.on('data', function (buf) {
|
|
content += buf.toString();
|
|
});
|
|
process.stdin.on('end', function () {
|
|
let messages = {};
|
|
|
|
// Since we send a few http requests to setup the process, we don't want
|
|
// to run this file one time per code analysis tool. Instead, we write all
|
|
// the results in the same stdin stream.
|
|
// The format of this stream is
|
|
//
|
|
// name-of-the-converter
|
|
// {"json":"payload"}
|
|
// name-of-the-other-converter
|
|
// {"other": ["json", "payload"]}
|
|
//
|
|
// In order to generate such stream, here is a sample bash command:
|
|
//
|
|
// cat <(echo eslint; npm run lint --silent -- --format=json; echo flow; flow --json) | node code-analysis-bot.js
|
|
|
|
const lines = content.trim().split('\n');
|
|
for (let i = 0; i < Math.ceil(lines.length / 2); ++i) {
|
|
const converter = converters[lines[i * 2]];
|
|
if (!converter) {
|
|
throw new Error('Unknown converter ' + lines[i * 2]);
|
|
}
|
|
let json;
|
|
try {
|
|
json = JSON.parse(lines[i * 2 + 1]);
|
|
} catch (e) {}
|
|
|
|
converter(messages, json);
|
|
}
|
|
|
|
// The paths are returned in absolute from code analysis tools but github works
|
|
// on paths relative from the root of the project. Doing the normalization here.
|
|
const pwd = path.resolve('.');
|
|
for (let absolutePath in messages) {
|
|
const relativePath = path.relative(pwd, absolutePath);
|
|
if (relativePath === absolutePath) {
|
|
continue;
|
|
}
|
|
messages[relativePath] = messages[absolutePath];
|
|
delete messages[absolutePath];
|
|
}
|
|
|
|
const owner = process.env.GITHUB_OWNER;
|
|
const repo = process.env.GITHUB_REPO;
|
|
|
|
if (!process.env.GITHUB_PR_NUMBER) {
|
|
console.error(
|
|
'Missing GITHUB_PR_NUMBER. Example: 4687. Review feedback with code analysis results cannot be provided on GitHub without a valid pull request number.',
|
|
);
|
|
// for master branch, don't throw an error
|
|
process.exit(0);
|
|
}
|
|
|
|
const number = process.env.GITHUB_PR_NUMBER;
|
|
|
|
(async () => {
|
|
await main(messages, owner, repo, number);
|
|
})();
|
|
});
|