As mentioned earlier, Patchron knowledge is based upon patches. Patch contains latest changes done to particular file. To understand it better, here is an example of how received data looks like:
"@@ -10,13 +10,7 @@ const payload = require('./fixtures/pull_request.opened');\n" +
" const fs = require('fs');\n" +
" const path = require('path');\n" +
' \n' +
'-const {\n' +
'- describe,\n' +
'- expect,\n' +
'- test,\n' +
"-} = require('@jest/globals');\n" +
"+const { expect, test } = require('@jest/globals');\n" +
' \n' +
'@@ -27,7 +21,6 @@ const deployment = {\n' +
" schema: 'rocks!',\n" +
' },\n' +
" environment: 'production',\n" +
`- description: "My Probot App's first deploy!",\n` +
' };\n' +If you aren't familiar with patch indicators, here is short explanation:
- line that was added starts with
+ - line that was removed starts with
- - line that was unchanged starts with
whitespace - line that begins with
@@is hunk header. It allows to identify lines in respect to source file. It also informs about hunk length.
Hunk header e.g. @@ -10,13 +10,7 @@ contains following information:
- LEFT SIDE
-10,13- 10 is number of first line that starts below hunk header
- 13 is left side hunk length (sum of unchanged and removed lines)
- RIGHT SIDE
+10,7- 10 is number of first line that starts below hunk header
- 7 is right side hunk length (sum of unchanged and added lines)
In all rules that analyze source code, received data is organized via setupData method that "unpacks" patch content into collection. Each object contains information mentioned below:
{
index: number,
indentation: number,
content: string,
trimmedContent: string
}Rules that are intended to focus on more than single line of patch are using getMultiLineStructure helper. It allows to pass multiLineOptions array. Each array's object is built in the following manner:
{
indicator: object|null, // first line of multi-line
limiter: object|Array<object> // last line of multi-line
}For more details on how those objects can be arranged, refer to general type definitions, implementation itself or examples.
In each rule we can mark out 3 to 4 steps: 1) loading configuration, 2) setting up data (does not refer to pull rules), 3) performing review and 4) returning comments. The things that we need to focus on are:
- awareness of how
file.splitPatch(anddataafterthis.setupData(splitPatch)) looks like - concept how to determine case that should be commented out to the PR owner
Once project is forked/pulled, as first step in rule creation, consider pinning to IDE type definitions. To start scratching rule, copy structure from another file implementation or refer to template given below and start coding π If you prefer to write tests first, feel free to do so π
const BaseRule = require('src/rules/Base');
class TemplateRule extends BaseRule {
/**
* @param {PatchronContext} patchronContext
* @param {object} config
* @param {Patch} file
*/
constructor(patchronContext, config, file) {
super(patchronContext, file);
// 0. load rule config (if needed)
}
invoke() {
const reviewComments = [];
// 1. setup data (if rule targets patch content)
const { splitPatch } = this.file;
const data = this.setupData(splitPatch);
// 2. determine wrong cases and add comments to array
// 3. done
return reviewComments;
}
/**
* @returns {string}
*/
_getCommentBody(param1, param2) {
// if comment has complicated markdown structure (like in MarkedCommentsRule) use `dedent-js`
return '';
}
}
module.exports = TemplateRule;