Skip to content

Commit

Permalink
v2: Add support for commenting on PRs and Markdown Summary (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
bugale committed Sep 2, 2023
1 parent 6a5a4ee commit e3dadf4
Show file tree
Hide file tree
Showing 19 changed files with 8,518 additions and 759 deletions.
1 change: 1 addition & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ rules:
'i18n-text/no-en': 'off'
'@typescript-eslint/space-before-function-paren': 'off'
'generator-star-spacing': 'off'
'prettier/prettier': ['error', { 'endOfLine': 'auto' }]
4 changes: 3 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ jobs:
run: npm clean-install; npm install @microsoft/eslint-formatter-sarif
- name: Run ESLint
run: npx eslint . --format @microsoft/eslint-formatter-sarif --output-file lint.sarif
- name: Print Output
if: always()
run: cat lint.sarif
- name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v2
if: always()
Expand All @@ -37,7 +40,6 @@ jobs:
inputFile: 'lint.txt'
toolName: 'mdl'
inputFormat: 'mdl'
verbose: 'true'
- name: Upload results
uses: github/codeql-action/upload-sarif@v2
if: always()
Expand Down
15 changes: 9 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,18 @@ jobs:
matrix:
run:
[
{ linter: 'flake8', format: 'flake8', regex: '', levelMap: '' },
{ linter: 'mypy', format: 'mypy', regex: '', levelMap: '' },
{ linter: 'pylint', format: 'pylint', regex: '', levelMap: '' },
{ linter: 'mdl', format: 'mdl', regex: '', levelMap: '' },
{ linter: 'flake8', format: 'flake8', regex: '', levelMap: '', analysisPath: '.' },
{ linter: 'mypy', format: 'mypy', regex: '', levelMap: '', analysisPath: '.' },
{ linter: 'pylint', format: 'pylint', regex: '', levelMap: '', analysisPath: '.' },
{ linter: 'mdl', format: 'mdl', regex: '', levelMap: '', analysisPath: '.' },
{ linter: 'sarif', format: 'sarif', regex: '', levelMap: '', analysisPath: '.' },
{ linter: 'flake8subpath', format: 'flake8', regex: '', levelMap: '', analysisPath: 'A\B' },
{
linter: 'custom',
format: '',
regex: '^(?<path>[^-\n]+)(?:-(?<line>\d+))?(?:-(?<col>\d+))?(?:-(?<eline>\d+))?(?:-(?<ecol>\d+))? (?<level>[^:\s]+):(?<id>[^:\s]+):(?<sym>[^:\s]+) (?<msg>.+)$',
levelMap: '{ "err": "error", "warn": "warning", "info": "note" }'
levelMap: '{ "err": "error", "warn": "warning", "info": "note" }',
analysisPath: '.'
}
]
steps:
Expand All @@ -68,7 +71,7 @@ jobs:
inputFormat: ${{ matrix.run.format }}
inputRegex: ${{ matrix.run.regex }}
levelMap: ${{ matrix.run.levelMap }}
verbose: 'true'
analysisPath: ${{ matrix.run.analysisPath }}
- name: Create Diff
run: json-diff "__tests__/${{ matrix.run.linter }}.output.json" "sarif.json" | tee diff.txt
- name: Test Output
Expand Down
1 change: 1 addition & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"printWidth": 160,
"singleQuote": true,
"trailingComma": "none",
"endOfLine": "auto",
"semi": false
}
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Bugalint

This GitHub Action converts various linter outputs to SARIF, and supports custom linter output formats using regular expressions.
This GitHub Action converts various linter outputs to standard formats (including SARIF), and supports custom linter output formats using regular expressions.
This action can be used in conjunction with
[GitHub's Code Scanning feature](https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/about-code-scanning) to
[report linter issues as code scanning alerts](#basic-example).
This action can also auto-comment on the PR in the relevant lines, which is an alternative to uploading a SARIF (which is only available with GitHub Advanced Security).
This action can also generate a markdown report for the job.

## Usage

Expand All @@ -17,7 +19,7 @@ steps:
- uses: actions/setup-python@v4
- run: pip install pylint
- run: pylint --output-format=json my_python_file.py > lint.txt
- uses: bugale/bugalint@v1
- uses: bugale/bugalint@v2
if: always()
with:
inputFile: lint.txt
Expand All @@ -33,10 +35,17 @@ steps:
- `inputFile`: _(required)_ The path to the input file, i.e. the file containing the output of the linter.

- `sarif`: True by default - generates a SARIF file as an output. If set to false, the action will not generate a SARIF file.

- `comment`: Set to true to comment on the PR with the issues. If set to false or ommitted, the action will not comment on the PR.

- `summary`: True by default - generates a markdown summary for the job. If set to false, the action will not generate a markdown summary.

- `outputFile`: The path to the output SARIF file this action should generate. If not specified, the action will generate a `sarif.json` file in the root of
the repository.

- `toolName`: _(required)_ The `tool name` that will be written in the SARIF output. This is required per SARIF's schema.
- `toolName`: _(required)_ The `tool name` that will be written in the SARIF output. This is used by both code scanning and auto-pr-commenting to resolve fixed
issues.

- `inputFormat`: The name of a linter output format that this action [natively supports](#natively-supported-linter-output-formats). If not specified, the
action will expect `inputRegex` input to be specified.
Expand All @@ -48,7 +57,11 @@ steps:
- `levelMap`: An optional JSON object mapping between the linter's levels and the SARIF levels (`note`/`warning`/`error`). Ignored unless `inputRegex` is
specified.

- `verbose`: Causes the action to print it's input and output. Useful for debugging.
- `analysisPath`: The path to the directory from which the analysis was run, relative to the repository's root. By default, the action will use the repository's
root. This is required only when the linter's output contains paths that are relative but not to the repository's root, for which this action will
re-relativize them.

- `githubToken`: Relevant only for "comment" mode. The GitHub token to use to post the comment. If not specified, the action will use the action's token.

#### Natively Supported Linter Output Formats

Expand All @@ -63,6 +76,9 @@ This action supports a bunch of linter output formats, for which no `inputRegex`

- `markdownlint`: The format of [markdownlint](https://github.com/markdownlint/markdownlint) linter's default output.

- `SARIF`: A [standard format for static analysis](https://sarifweb.azurewebsites.net/). This is useful if you already have a SARIF file and want to create a summary
for it, or create comments on the PR.

#### Input Regex Named Groups

When using a custom regular expression, it must contains named groups for Bugalint to successfully understand which parts of each line are the issue's
Expand Down Expand Up @@ -107,7 +123,7 @@ steps:
- uses: actions/setup-python@v4
- run: pip install mylinter
- run: mylinter test.py > lint.txt
- uses: bugale/bugalint@v1
- uses: bugale/bugalint@v2
if: always()
with:
inputFile: lint.txt
Expand Down
19 changes: 11 additions & 8 deletions __tests__/bugalint.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import '@microsoft/jest-sarif'
import { readFileSync } from 'fs'
import { convert, getKnownParser, getRegexParser, type Parser } from '../src/bugalint'
import { generateSarif, getKnownParser, getRegexParser, type Parser } from '../src/bugalint'

describe('fullConversion', () => {
it.each([
['mypy', undefined],
['pylint', undefined],
['flake8', undefined],
['mdl', undefined],
['mypy', getKnownParser('mypy'), '.'],
['pylint', getKnownParser('pylint'), '.'],
['flake8', getKnownParser('flake8'), '.'],
['mdl', getKnownParser('mdl'), '.'],
['sarif', getKnownParser('sarif'), '.'],
['flake8subpath', getKnownParser('flake8'), 'A\\B'],
[
'custom',
getRegexParser(
/^(?<path>[^-\n]+)(?:-(?<line>\d+))?(?:-(?<col>\d+))?(?:-(?<eline>\d+))?(?:-(?<ecol>\d+))? (?<level>[^:\s]+):(?<id>[^:\s]+):(?<sym>[^:\s]+) (?<msg>.+)$/gm,
{ err: 'error', warn: 'warning', info: 'note' }
)
),
'.'
]
])('%s', (name: string, parser?: Parser) => {
])('%s', (name: string, parser: Parser, analysisPath: string) => {
const input = readFileSync(`__tests__/${name}.input.txt`, 'utf-8')
const output = readFileSync(`__tests__/${name}.output.json`, 'utf-8')
const result = convert(parser ?? getKnownParser(name), input, 'test')
const result = generateSarif(parser(input), 'test', analysisPath)
expect(result).toBeValidSarifLog()
expect(JSON.parse(JSON.stringify(result))).toStrictEqual(JSON.parse(output))
})
Expand Down
2 changes: 2 additions & 0 deletions __tests__/flake8subpath.input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.\main.py:85:21: E241 multiple spaces after ':'
.\c\d\__init__.py:139:1: E305 expected 2 blank lines after class or function definition, found 1
29 changes: 29 additions & 0 deletions __tests__/flake8subpath.output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"version": "2.1.0",
"$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.6",
"runs": [
{
"tool": { "driver": { "name": "test", "rules": [] } },
"results": [
{
"locations": [
{
"physicalLocation": { "artifactLocation": { "uri": "A/B/main.py" }, "region": { "startColumn": 21, "startLine": 85 } }
}
],
"message": { "text": "multiple spaces after ':'" },
"ruleId": "E241"
},
{
"locations": [
{
"physicalLocation": { "artifactLocation": { "uri": "A/B/c/d/__init__.py" }, "region": { "startColumn": 1, "startLine": 139 } }
}
],
"message": { "text": "expected 2 blank lines after class or function definition, found 1" },
"ruleId": "E305"
}
]
}
]
}
53 changes: 53 additions & 0 deletions __tests__/sarif.input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"version": "2.1.0",
"$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.6",
"runs": [
{
"tool": {
"driver": {
"name": "test",
"rules": [
{ "id": "N123", "name": "some-note" },
{ "id": "E124", "name": "some-warning" },
{ "id": "E125", "name": "some-error" }
]
}
},
"results": [
{
"locations": [
{
"physicalLocation": { "artifactLocation": { "uri": "test.py" }, "region": { "endColumn": 10, "endLine": 4, "startColumn": 1, "startLine": 3 } }
}
],
"level": "note",
"message": { "text": "Message 1" },
"ruleId": "N123",
"ruleIndex": 0
},
{
"locations": [
{
"physicalLocation": { "artifactLocation": { "uri": "test.py" }, "region": { "startColumn": 1, "startLine": 10 } }
}
],
"level": "warning",
"message": { "text": "Message 2" },
"ruleId": "E124",
"ruleIndex": 1
},
{
"locations": [
{
"physicalLocation": { "artifactLocation": { "uri": "test.py" } }
}
],
"level": "error",
"message": { "text": "Message 3" },
"ruleId": "E125",
"ruleIndex": 2
}
]
}
]
}
53 changes: 53 additions & 0 deletions __tests__/sarif.output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"version": "2.1.0",
"$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.6",
"runs": [
{
"tool": {
"driver": {
"name": "test",
"rules": [
{ "id": "N123", "name": "some-note" },
{ "id": "E124", "name": "some-warning" },
{ "id": "E125", "name": "some-error" }
]
}
},
"results": [
{
"locations": [
{
"physicalLocation": { "artifactLocation": { "uri": "test.py" }, "region": { "endColumn": 10, "endLine": 4, "startColumn": 1, "startLine": 3 } }
}
],
"level": "note",
"message": { "text": "Message 1" },
"ruleId": "N123",
"ruleIndex": 0
},
{
"locations": [
{
"physicalLocation": { "artifactLocation": { "uri": "test.py" }, "region": { "startColumn": 1, "startLine": 10 } }
}
],
"level": "warning",
"message": { "text": "Message 2" },
"ruleId": "E124",
"ruleIndex": 1
},
{
"locations": [
{
"physicalLocation": { "artifactLocation": { "uri": "test.py" } }
}
],
"level": "error",
"message": { "text": "Message 3" },
"ruleId": "E125",
"ruleIndex": 2
}
]
}
]
}
24 changes: 20 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
name: 'bugalint'
description: 'Convert various linter outputs to SARIF'
description: 'Convert various linter outputs to standard formats'
inputs:
inputFile:
description: 'Path to the file with the linter output'
required: true
sarif:
description: 'Should the action create a SARIF file'
required: false
default: 'true'
comment:
description: 'Should the action comment on the PR'
required: false
default: 'false'
summary:
description: 'Should the action create a markdown summary'
required: false
default: 'true'
outputFile:
description: 'Path to the SARIF file this action will generate'
required: false
Expand All @@ -23,10 +35,14 @@ inputs:
description: 'Mapping from levels in input to SARIF levels (note, warning, error)'
required: false
default: ''
verbose:
description: 'Print raw input and output'
analysisPath:
description: "The path at which the analysis took place relative to the repository's root. Used to relativize any paths to the repository root path."
required: false
default: '.'
githubToken:
description: 'Github token of the repository (automatically created by Github)'
default: ${{ github.token }}
required: false
default: 'false'
runs:
using: 'node16'
main: 'dist/index.js'
Loading

0 comments on commit e3dadf4

Please sign in to comment.