CodeClimate Quality の後継 "Qlty" を触ってみた

## はじめに

社内で運用されていた [CodeClimate Quality](https://codeclimate.com/quality) （以下CCQ）の CLI を調査していたところ
[Qlty](https://docs.qlty.sh/what-is-qlty) に変わっていたのを検知したので、触ってみました。

元々 CodeClimate 社は Velocity と Quality というサービスを展開していましたが、Quality ごと Qlty Software 社へスピンアウトしていました。（びっくりしました）
参考 ：https://codeclimate.com/blog/code-climate-quality-is-now-qlty-software 

## Qlty の概要

従来と大きくコンセプトは変わらず、複数のプログラミング言語に対応した静的コード解析ツールです。

### 主要機能：
参考：https://docs.qlty.sh/what-is-qlty 
> 🐞 Linting (for every language)
> 🖌️ Auto-formatting
> 🚨 Security scanning (IaC, SAST, SCA, and more)
> 💩 Maintainability and duplication
> 🚦 Code coverage

Qlty CLI と Qlty Cloud があり、用途によって組み合わせたり、使い分けます。

Cloud が CI として連携して分析・可視化する部分、CLIはローカルでの分析に使うという位置づけのようです。

CCQ 時代の CLI では Maintainalibity and duplication がメイン（だった記憶）でしたが、上述の他の機能も使えるようになったようです。

今回はCLIを試してみました。

## super-linterと比較
使用感の前に同じく社内で利用されていた [super-linter](https://github.com/super-linter/super-linter) とざっくり比較してみましょう。

super-linterは各言語のデファクトなLinterやAuto formatterを簡単に利用できるツールです。

2024/12 現在のサポート言語は super-linter 59種類、 Qlty 25種類です。
Qltyは完全サポートとプラグインサポートの2つの方法で利用でき、完全サポートされる言語は9種類です。
完全サポートでサポートされるのは上述の5つのメイン機能です。
Maintainablity や Code coverage まで扱うのでそのあたりは Qlty のほうがより深くサポートされているようです。

広く浅いsuper-linter、狭く深いQltyのような状況だと思います。

Qlty の運用面での特徴は調査しきれていませんが、気軽に使えるのは super-linter でしょう。

## Qlty CLI を使ってみる 

[インストール](https://docs.qlty.sh/cli/quickstart)して実際に動かして行きます。

下記がメインのコマンドです。
- `smells` : コードの悪臭（潜在的な問題）を検出する
- `check` : 各種Linter (rubocop, brakemanなど) を実行し、コードの静的解析を行う
- `fmt` : コードのフォーマットを自動的に修正する（Linterの機能）
- `metrics` : コードの複雑度や行数などのメトリクスを出力する


まずは、名前が気になった `smells` コマンドから試してみることにします。

### `smells`

オプション無しで実行すると upstream ブランチに対する差分に対して実行されます。
例だと origin/master ブランチとの差分に対して実行されました。
差分がない場合、0 fileになっています。

```bash
qlty smells                                           
     [0/3] 🔍  Analyzing vs. origin/master... 0.04s
     [1/3] 👀  Checking structure of 0 files...  0.00s
     [2/3] 🤔  Looking for duplication across 0 files...  0.00s
     [3/3] ✨  Reporting...  

```

`--all` オプションを指定すると、全てのファイルに対して分析を実行します。
```bash
qlty smells --all
     [0/3] 🔍  Analyzing all targets... 0.08s
     [1/3] 👀  Checking structure of 23 files...  0.66s
     [2/3] 🤔  Looking for duplication across 23 files...  0.05s
     [3/3] ✨  Reporting...  

bin/webpack
    Found 16 lines of similar code in 2 locations (mass = 76)

           1  #!/usr/bin/env ruby
           2  
           3  ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
           4  ENV["NODE_ENV"]  ||= ENV["NODE_ENV"] || "development"
           5  
           6  require "pathname"
              [hid 10 additional lines]
        also found at bin/webpack-dev-server


bin/webpack-dev-server
    Found 16 lines of similar code in 2 locations (mass = 76)

           1  #!/usr/bin/env ruby
           2  
           3  ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
           4  ENV["NODE_ENV"]  ||= ENV["NODE_ENV"] || "development"
           5  
           6  require "pathname"
              [hid 10 additional lines]
        also found at bin/webpack

```

### `check`
rubocop や brakeman などのデファクトな Linter が実行されます。
各種 Lint ツールは`qlty init`実行時にインストールされます（これが結構便利）


```bash
qlty check                          
     [0/1] 🤔  Planning...  0.44s
     [1/1] 🔍  Analyzing vs. origin/master... 

 ∟ brakeman  ⠐
 ∟ rubocop app/controllers/application_controller.rb and 3 more ⠐
 ∟ reek app/controllers/application_controller.rb and 3 more ⡀
 ∟ trufflehog app/controllers/application_controller.rb and 5 more ⠐       

 ISSUES 

app/controllers/application_controller.rb:0:0
    0:0  fmt     Incorrect formatting, autoformat by running `qlty fmt`.  rubocop:fmt
    4:5  medium  Extra empty line detected at class body beginning.       rubocop:Layout/EmptyLinesAroundClassBody

app/controllers/pages_controller.rb:0:0
    0:0  fmt     Incorrect formatting, autoformat by running `qlty fmt`.  rubocop:fmt
    5:6  medium  Extra empty line detected at method body beginning.      rubocop:Layout/EmptyLinesAroundMethodBody

app/controllers/test_reports_controller.rb:0:0
    0:0  fmt     Incorrect formatting, autoformat by running `qlty fmt`.  rubocop:fmt
    7:7  medium  Trailing whitespace detected.                            rubocop:Layout/TrailingWhitespace

Checked 23 files
✖ 6 issues
```

実行時の出力を見ていたら`trivy`も実行されていました。
`qlty init` コマンド実行時に自動生成される `.qlty/qlty.toml` ファイルには、実行されるプラグインが明記されています。

```toml
[[plugin]]
name = "brakeman"

[[plugin]]
name = "checkov"

[[plugin]]
name = "eslint"
version = "8.57.0"

[[plugin]]
name = "golangci-lint"

[[plugin]]
name = "markdownlint"
version = "0.41.0"

[[plugin]]
name = "osv-scanner"

[[plugin]]
name = "prettier"

[[plugin]]
name = "reek"

[[plugin]]
name = "ripgrep"

[[plugin]]
name = "rubocop"
version = "1.65.1"
package_file = "Gemfile"
package_filters = ["rubocop"]

[[plugin]]
name = "stylelint"
version = "14.6.1"

[[plugin]]
name = "trivy"
drivers = [
  "config",
  "fs-vuln",
]

[[plugin]]
name = "trufflehog"

[[plugin]]
name = "yamllint"
```

### `metrics`

コードに関する指標が出力されます。
個人的には、JSONなどのプログラマブルな出力形式が提供されると、他のツールとの連携やさらなる分析が可能になり、より便利になると思いました。

```bash
qlty metrics --all --max-depth 2
     [0/3] 🤔  Planning...  0.26s
     [1/3] 🔍  Analyzing files over all targets... 0.91s
     [2/3] 👀  Parsing 23 files...  0.00s
     [3/3] ✨  Reporting...  
 name            | classes | funcs | fields | cyclo | complex | LCOM | lines | LOC 
-----------------+---------+-------+--------+-------+---------+------+-------+-----
 app             |       8 |    10 |      2 |    25 |       2 |    2 |   202 | 126 
 app/channels    |       2 |     0 |      0 |     2 |       0 |    0 |    12 |   8 
 app/client      |       0 |     0 |      0 |     2 |       0 |    0 |    38 |  19 
 app/controllers |       2 |     2 |      2 |     4 |       0 |    2 |    33 |  25 
 app/helpers     |       0 |     1 |      0 |     2 |       2 |    0 |    12 |  10 
 app/jobs        |       1 |     0 |      0 |     1 |       0 |    0 |     4 |   2 
 app/models      |       3 |     7 |      0 |    14 |       0 |    0 |   103 |  62 
 bin             |       0 |     2 |      0 |    22 |      11 |    0 |   114 |  64 
 lib             |       0 |     0 |      0 |     2 |       1 |    0 |    55 |  47 
 lib/tasks       |       0 |     0 |      0 |     2 |       1 |    0 |    55 |  47 
 TOTAL           |      16 |    22 |      4 |    76 |      17 |    4 |   628 | 410
```

### `fmt`

plugin に基づいて自動修正するコマンド。多分 rubocop の自動修正が動いたようでした。

何による修正なのかわからないので、`--filter` オプションで動作する plugin を指定するのがいいのかも。

```bash
qlty fmt                                   
✔ Formatted app/controllers/test_reports_controller.rb
✔ Formatted app/controllers/pages_controller.rb
✔ Formatted app/controllers/application_controller.rb
Checked 4 files
```

### `--upstream` オプション
個人的には共通の `--upstream` オプションが便利だと思いました。
これがないと CI とかで実行するときにファイル差分からいちいち実行対象を抽出する手間がかかります。

動きとしては指定したブランチとの差分に対して実行されます。

```bash
qlty check --upstream origin/resolve-conflicts
     [0/1] 🤔  Planning...  0.15s
     [1/1] 🔍  Analyzing vs. origin/resolve-conflicts... 

```

```bash
qlty metrics --upstream origin/resolve-conflicts
     [0/3] 🤔  Planning...  0.14s
     [1/3] 🔍  Analyzing files vs. origin/resolve-conflicts... 0.31s
     [2/3] 👀  Parsing 6 files...  0.00s
     [3/3] ✨  Reporting...  
 name                                      | classes | funcs | fields | cyclo | complex | LCOM | lines | LOC 
-------------------------------------------+---------+-------+--------+-------+---------+------+-------+-----
 Gemfile                                   |       0 |     0 |      0 |     1 |       0 |    0 |    53 |  47 
 app/controllers/application_controller.rb |       1 |     1 |      0 |     1 |       0 |    1 |    16 |  11 
 app/controllers/pages_controller.rb       |       1 |     1 |      2 |     3 |       0 |    1 |    17 |  14 
 app/helpers/application_helper.rb         |       0 |     1 |      0 |     2 |       2 |    0 |    12 |  10 
 app/models/job.rb                         |       1 |     7 |      0 |    12 |       0 |    0 |    85 |  56 
 lib/tasks/auto_annotate_models.rake       |       0 |     0 |      0 |     2 |       1 |    0 |    55 |  47 
 TOTAL                                     |       3 |    10 |      2 |    21 |       3 |    2 |   238 | 185
```

### `migrate`
あまり需要なさそうですが、CCQの設定ファイル `.codeclimate.yaml` をQltyの設定ファイル `qlty.toml` に移行するコマンドも用意されていました。
前からユーザーをきちんと考えてくれていて嬉しくなりました :smiley: 

例えば以下のような `.codeclimate.yaml` がある状態で、 `qlty migrate` を実行すると、
```yaml
version: "2"
checks:
  argument-count:
    enabled: true
    config:
      threshold: 4
  complex-logic:
    enabled: true
    config:
      threshold: 4
  file-lines:
    enabled: true
    config:
      threshold: 250
  method-complexity:
    enabled: true
    config:
      threshold: 5
  method-count:
    enabled: true
    config:
      threshold: 25
  method-lines:
    enabled: true
    config:
      threshold: 25
  nested-control-flow:
    enabled: true
    config:
      threshold: 4
  return-statements:
    enabled: true
    config:
      threshold: 4
  similar-code:
    enabled: false
    config:
      threshold:
  identical-code:
    enabled: false
    config:
      threshold:
```

以下の内容が `.qlty/qlty.toml` に追記されます。

```toml
[smells.boolean_logic]
threshold = 4
enabled = true

[smells.file_complexity]
threshold = 55
enabled = true

[smells.return_statements]
threshold = 4
enabled = true

[smells.nested_control_flow]
threshold = 4
enabled = true

[smells.function_parameters]
threshold = 4
enabled = true

[smells.function_complexity]
threshold = 5
enabled = true

[smells.duplication]
enabled = false
```

## 余談

試すときにインストールが上手くいかなくて[サポートに問い合わせ](https://github.com/orgs/qltysh/discussions/1256)したら、1~2日くらいで返答が来て解決できました :clap:

導入期だからかもですが、迅速で手厚いサポートも好感触でした。

機能のリクエストを行えば、取り入れてくれるかもしれませんね。


## 終わりに

ほぼほぼ公式ドキュメント見れば分かる話で恐縮ですが、使ってみた肌感が伝われば幸いです。

こういったツールは Copilot 系の AI によるコーディングツールで淘汰されるのかもとも思いましたが、AI は完璧ではない点もあるので多層でチェックする仕組みもありかなと思いました。

また、Qlty Cloud 側には AI による[説明](https://docs.qlty.sh/cloud/ai-explanations )や[自動修正](https://docs.qlty.sh/cloud/ai-autofixes)の提供が予定されているようなので、ほかツールとどう差別化を図るのかとても楽しみだなと思いました。

Fin.