■ はじめに
https://dk521123.hatenablog.com/entry/2024/04/13/232832
https://dk521123.hatenablog.com/entry/2024/04/18/161200
の続き。 reviewdog の Reviewdog Diagnostic Format (RDFormat) について 切り出して、取り上げる。
目次
【1】Reviewdog Diagnostic Format (RDFormat) 1)rdjson 2)rdjsonl 【2】RDFormat を使用した方法 【3】注意点 1)変換を jq -f <file>で行う場合、改行コードはLFにしておくこと 【4】サンプル 例1:SQLFluff - Jq バージョン 例2:SQLFluff - Python バージョン 【5】補足:「どうやって組み込んでいったか?」について 1)準備:ローカル環境構築 2)環境設定する上での注意点 3)出力フォーマット解析
【1】Reviewdog Diagnostic Format (RDFormat)
* reviewdog 独自のフォーマットである 「rdjson」又は「rdjsonl」に変換して Linterの結果を reviewdog に食わせる
https://github.com/reviewdog/reviewdog?tab=readme-ov-file#reviewdog-diagnostic-format-rdformat
1)rdjson
* Reviewdog Diagnostic JSON Format
https://github.com/reviewdog/reviewdog/tree/master/proto/rdf#rdjson
2)rdjsonl
* Reviewdog Diagnostic JSON Lines Format
https://github.com/reviewdog/reviewdog/tree/master/proto/rdf#rdjsonl
【2】RDFormat を使用した方法
* 以下の公式ドキュメントより抜粋
https://github.com/reviewdog/reviewdog?tab=readme-ov-file#reviewdog-diagnostic-format-rdformat
全体イメージ
# Linter の結果を、jq などで「rdjson」又は「rdjsonl」に変換すればいいだけ $ <linter> | <convert-to-rdjson> | reviewdog -f=rdjson -reporter=github-pr-review # or $ <linter> | <convert-to-rdjsonl> | reviewdog -f=rdjsonl -reporter=github-pr-review
【3】注意点
1)変換を jq -f で行う場合、改行コードはLFにしておくこと
下記「例1:SQLFluff - jq バージョン」のように、 jq -f <file>で、「rdjson」又は「rdjsonl」に変換する場合、 ファイル(<file>)の改行コードは、CRLFではなくLFにしておくこと => 以下「エラーメッセージ」のようになってしまう可能性がある
エラーメッセージ
jq: error: syntax error, unexpected INVALID_CHARACTER (Unix shell quoting issues?) at <top-level>, line1: {
【4】サンプル
https://dk521123.hatenablog.com/entry/2024/04/18/161200
で行ったThird party で行った SQLFluff with reviewdog を Third partyを使わず独自で かつ 最新 SQLFluff v3 対応版で実装してみる
例1:SQLFluff - jq バージョン
.github/workflows/demo_reviewdogs.yml
name: DemoForReviewdogs on: - pull_request env: REVIEWDOG_GITHUB_API_TOKEN: "${{ secrets.REVIEWDOG_GITHUB_API_TOKEN }}" jobs: demo-job: name: lint runs-on: ubuntu-latest steps: - name: Run checkout uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip # pip install sqlfluff pip install sqlfluff==3.0.4 # Step1: Install reviewdog - uses: reviewdog/action-setup@v1 with: reviewdog_version: latest # Step2: Lint - name: lint with SQLFluff run: | for sql_file in `find sqls/ -name '*.sql'` do echo "sql_file=$sql_file" sqlfluff lint $sql_file --dialect snowflake --format json | jq -f .github/template.jq | reviewdog -name="sqlfluff" -f=rdjson -reporter=github-pr-review done
.github/template.jq (★注意点:改行コードはLFにしておくこと★)
{ source: { name: "sqlfluff", url: "https://github.com/sqlfluff/sqlfluff" }, severity: "ERROR", diagnostics: (. // {}) | map(. as $file | $file.violations[] as $violation | (if $file.violations[].warning == true then "WARNING" else "ERROR" end) as $severity | { message: "[\($violation.name)] - \($violation.description)", location: { path: $file.filepath, range: { start: { line: $violation.start_line_no, column: $violation.start_line_pos }, end: { line: $violation.end_line_no, column: $violation.end_line_pos }, } }, suggestions: ([$violation.fixes[] as $suggestion | { range: { start: { line: $suggestion.start_line_no, column: $suggestion.start_line_pos }, end: { line: $suggestion.end_line_no, column: $suggestion.end_line_pos } }, text: $suggestion.edit }]), severity: $severity, code: { value: $violation.code, url: "https://docs.sqlfluff.com/en/stable/rules.html#rule-\($violation.code)" }, }) }
SELECT a + b FROM tbl;
例2:SQLFluff - Python バージョン
今回、<convert-to-rdjson>部分を jq コマンドを使っているが、 複雑で込み入った処理になった場合の汎用性がないので、 Python使ってもいいかも、、、
.github/workflows/demo_reviewdogs.yml
# 例1との変更点 + ついでにエラーハンドリング # Step2: Lint - id: lint-with-sqlfluff name: lint with SQLFluff env: RESULT_FILE_PATH: ./result_from_sqlfluff.json RDJSON_FILE_PATH: ./result_with_rdjson.json shell: /usr/bin/bash {0} run: | is_error=false for sql_file in `find sqls/ -name '*.sql'` do echo "sql_file=$sql_file" # Run linter sqlfluff lint $sql_file --dialect snowflake --format json --write-output ${{ env.RESULT_FILE_PATH }} python .github/convert.py ${{ env.RESULT_FILE_PATH }} ${{ env.RDJSON_FILE_PATH }} cat ${{ env.RDJSON_FILE_PATH }} | reviewdog -name="sqlfluff" -tee -fail-on-error -f=rdjson -reporter=github-pr-review if [ $? -ne 0 ] ; then is_error=true fi rm ${{ env.RESULT_FILE_PATH }} 2> /dev/null rm ${{ env.RDJSON_FILE_PATH }} 2> /dev/null done if [ "$is_error" == "true" ] ; then echo "Error" exit 1 else echo "Success!" fi
.github/convert.py (変換用Python)
import json import sys def get_severity(is_warning): return "WARNING" if is_warning else "ERROR" def main(input_file, output_file): # Step1: Read JSON with open(input_file, "r", newline="\n") as in_file: input_json_dict = json.load(in_file) # Step2: Convert diagnostics = [] has_error = False for input in input_json_dict: path = input.get("filepath") for violation in input.get("violations"): is_warning = violation.get("warning", True) if not is_warning: has_error = True severity = get_severity(is_warning) suggestions = [] for fix in violation.get("fixes"): suggestion = { "range": { "start": { "line": fix.get("start_line_no"), "column": fix.get("start_line_pos") }, "end": { "line": fix.get("end_line_no"), "column": fix.get("end_line_pos") } }, "text": fix.get("edit") } suggestions.append(suggestion) diagnostic = { "message": f"[{violation.get('name')}] - {violation.get('description')}", "location": { "path": path, "range": { "start": { "line": violation.get("start_line_no"), "column": violation.get("start_line_pos") }, "end": { "line": violation.get("end_line_no"), "column": violation.get("end_line_pos") }, } }, "suggestions": suggestions, "severity": severity, "code": { "value": violation.get("code"), "url": f"https://docs.sqlfluff.com/en/stable/rules.html#rule-{violation.get('code')}" } } diagnostics.append(diagnostic) main_severity = get_severity(not has_error) output_json_dict = { "source": { "name": "sqlfluff", "url": "https://github.com/sqlfluff/sqlfluff" }, "severity": main_severity, "diagnostics": diagnostics } # Step3: Write JSON with open(output_file, "w", newline="\n") as out_file: json.dump(output_json_dict, out_file) if __name__ == "__main__": if len(sys.argv) > 3: print("[How to use]") print(" python convert.py input.json ouptput.json") print(" args1: Input file with json from SQLFluff (e.g. input.json)") print(" args2: Output file with json for reviewdog (e.g. ouptput.json)") else: input_file = sys.argv[1] output_file = sys.argv[2] main(input_file, output_file)
【5】補足:「どうやって組み込んでいったか?」について
* 「例1:SQLFluff」で実際にどうやって組み込んでいったかについてメモしておく
1)準備:ローカル環境構築
* フォーマットが分かっていない状況で、Github Actionsを動かしながら実装するのは めんどいので、まずは、ローカル環境に以下をインストールした ~~~~~ + Linter(今回は SQLFluff)」 + reviewdog ~~~~~ => インストールについては、以下の関連記事を参照のこと
SQL Linter ~ SQLFluff ~
https://dk521123.hatenablog.com/entry/2024/02/28/225002
Github ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2019/07/18/234652
2)環境設定する上での注意点
* Linterはバージョン違いにより出力フォーマットが異なることもあるので Github Actionsを動かす環境とローカル環境のバージョンを一致させること (バージョン固定してもいいかも)
出力結果 - SQLFluff v2.3.5
[ { "filepath": "test.sql", "violations": [ { "line_no": 1, "line_pos": 1, "code": "LT01", "description": "Expected only single space before 'SELECT' keyword. Found ' '.", "name": "layout.spacing" }, ・・・ { "line_no": 1, "line_pos": 27, "code": "LT01", "description": "Unnecessary trailing whitespace.", "name": "layout.spacing" } ] } ]
出力結果 - SQLFluff v3.0.4 (上の「SQLFluff v2.3.5」と比較)
[ { "filepath": "test.sql", "violations": [ { "start_line_no": 1, "start_line_pos": 1, "code": "LT01", "description": "Expected only single space before 'SELECT' keyword. Found ' '.", "name": "layout.spacing", "warning": false, "fixes": [ { "type": "replace", "edit": " ", "start_line_no": 1, "start_line_pos": 1, "start_file_pos": 0, "end_line_no": 1, "end_line_pos": 3, "end_file_pos": 2 } ], "start_file_pos": 0, "end_line_no": 1, "end_line_pos": 3, "end_file_pos": 2 }, ・・・ { "start_line_no": 1, "start_line_pos": 27, "code": "LT01", "description": "Unnecessary trailing whitespace.", "name": "layout.spacing", "warning": false, "fixes": [ { "type": "delete", "edit": "", "start_line_no": 1, "start_line_pos": 27, "start_file_pos": 26, "end_line_no": 1, "end_line_pos": 29, "end_file_pos": 28 } ], "start_file_pos": 26, "end_line_no": 1, "end_line_pos": 29, "end_file_pos": 28 } ], "statistics": { "source_chars": 29, "templated_chars": 29, "segments": 33, "raw_segments": 20 }, "timings": { "templating": 0.0033749999997780833, "lexing": 0.0008491999997204402, "parsing": 0.0106869999999617, "linting": 0.027636198999971384, "AL01": 0.00011649999987639603, ・・・ "TQ01": 2.769999991869554e-05 } } ]
3)出力フォーマット解析
* 後は、実際に出力したりして、 どういったフォーマットになっているかをみて 寄せていくだけ
コマンド例
# とりあえず、実際に出力 $ sqlfluff lint test.sql --dialect ansi --format json # 見づらいので、jqを間にかます $ sqlfluff lint test.sql --dialect ansi --format json | jq # jqコマンドでrdjsonフォーマットに合うように試してみる $ sqlfluff lint test.sql --dialect ansi --format json | jq -f template.jq # 最後に実際のコマンドを実行してみる $ sqlfluff lint test.sql --dialect ansi --format json | jq -f template.jq | reviewdog -f=rdjson -name="hello" -reporter=github-pr-review
関連記事
reviewdog ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2024/04/13/232832
Github ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2019/07/18/234652
Github Actions ~ 基礎知識編 ~
https://dk521123.hatenablog.com/entry/2021/11/04/142835
Github Actions ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2022/06/16/151443
Github Actions ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2023/12/22/195715
Github Actions ~ pull_request / pull_request_target ~
https://dk521123.hatenablog.com/entry/2024/04/10/152101
Github Actions ~ SQL Linter ~
https://dk521123.hatenablog.com/entry/2024/03/04/180308
SQL Linter ~ SQLFluff ~
https://dk521123.hatenablog.com/entry/2024/02/28/225002
GitHub CLI ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2024/02/17/233836
jq コマンド ~ JSON を扱う ~
https://dk521123.hatenablog.com/entry/2020/02/01/000000
シェル ~ ファイル処理あれこれ ~
https://dk521123.hatenablog.com/entry/2020/09/28/000000
JSON
https://dk521123.hatenablog.com/entry/2019/10/19/104805
JSONあれこれ
https://dk521123.hatenablog.com/entry/2022/02/14/000000