sonicop 26.9.100-aarch64-linux → 26.9.101-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb2e48b52a313f6e407570886d1caa2b395008807ab1cc815038b3654c958d45
4
- data.tar.gz: 8044e6af7d35a48b74f453b67f040ea74722481cad4f6209f0309ee38f42d596
3
+ metadata.gz: 7ae9ceb6c5f2f6c594ae3e8bcf4e28ba41987e87f62c60c8b2abbd948fc52b31
4
+ data.tar.gz: 917264ec9f6735cccdcb72e7c073dfd25108c997097774d91918ba6ec3779c01
5
5
  SHA512:
6
- metadata.gz: bd059ae5244f8fb9c5bced34991545112fcfeed1e23c3ac047b40d124466970454e464101f0edb25cf569988cba8f99979bdbba470d2635f3981cb312d06f435
7
- data.tar.gz: 2f5b6d5153e7b4e9017ea52d5b7ce575bfad1d2c91ce48eb919ae2c75fc64554665e3b30d5e9f6d65f5d416f40c661e0c700a76d65bee65a8aa94b1bec2d8a11
6
+ metadata.gz: 9da388bb572d7c23d2a32038d2924da2d7c20286fc98b9e89a477240338d1e409bdb98a612abaf5dda9c91ac1dc6c52319c74f089f954f07e8fad9a3a78ca0ad
7
+ data.tar.gz: dde37cff0326f7462e8bbd7a8833ad7eea70ebcd703231c079b0227a3c0138b43a88a23447d3166bc94bca1e31fb3bc50a6c3d0016bee57de560668ad9ff9ffb
data/README.ja.md CHANGED
@@ -1,55 +1,55 @@
1
- <h1 align="center">
2
- <img src="docs/images/sonicop_logo_header.png" width="600" alt="Sonicop">
3
- </h1>
1
+ <p align="center">
2
+ <img src="docs/images/sonicop_logo_header.png" width="320" alt="sonicop">
3
+ </p>
4
+
5
+ <h1 align="center">sonicop</h1>
4
6
 
5
7
  <p align="center">
6
- <strong>Rust で実装した高速なネイティブ RuboCop 互換 Ruby リンター/フォーマッター。</strong>
8
+ Rust で実装した高速なネイティブ RuboCop 互換 Ruby リンター/フォーマッター
7
9
  </p>
8
10
 
11
+ <!-- standard:badges:start -->
12
+ <h3 align="center">対応プラットフォーム</h3>
13
+
9
14
  <p align="center">
10
- <a href="https://github.com/owayo/sonicop/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/owayo/sonicop/actions/workflows/ci.yml/badge.svg?branch=main"></a>
11
- <a href="https://rubygems.org/gems/sonicop"><img alt="Gem Version" src="https://img.shields.io/gem/v/sonicop"></a>
12
- <a href="LICENSE"><img alt="License" src="https://img.shields.io/github/license/owayo/sonicop"></a>
15
+ <img src="https://img.shields.io/badge/Linux-FCC624?logo=linux&amp;logoColor=black" alt="Linux">
16
+ <img src="https://img.shields.io/badge/macOS-000000?logo=apple&amp;logoColor=white" alt="macOS">
17
+ <img src="https://img.shields.io/badge/Windows-0078D6" alt="Windows">
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="https://github.com/owayo/sonicop/actions/workflows/ci.yml"><img src="https://github.com/owayo/sonicop/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
22
+ <a href="https://github.com/owayo/sonicop/releases/latest"><img src="https://img.shields.io/github/v/release/owayo/sonicop" alt="Release"></a>
23
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/owayo/sonicop" alt="License"></a>
24
+ <a href="https://rubygems.org/gems/sonicop"><img src="https://img.shields.io/gem/v/sonicop" alt="RubyGems"></a>
13
25
  </p>
14
26
 
15
27
  <p align="center">
16
28
  <a href="README.md">English</a> |
17
29
  <a href="README.ja.md">日本語</a>
18
30
  </p>
31
+ <!-- standard:badges:end -->
19
32
 
20
33
  ---
21
34
 
22
- ## 概要
23
-
24
- Sonicop は、Ruby プロセスを起動せずに動作する高速な Rust 製 Ruby リンター/フォーマッターです。
25
- 既存の `.rubocop.yml` をそのまま利用でき、サブディレクトリごとの設定、設定の継承、対象ファイルの
26
- Include/Exclude、severity、自動修正設定にも対応しています。
35
+ Sonicop は、Ruby プロセスを起動せずに動作する高速な Rust 製 Ruby リンター/フォーマッターです。既存の `.rubocop.yml` をそのまま利用でき、サブディレクトリごとの設定、設定の継承、対象ファイルの Include/Exclude、severity、自動修正設定にも対応しています。
27
36
 
28
- 最新の Ruby 構文へ追従する
29
- [owayo/tree-sitter-ruby](https://github.com/owayo/tree-sitter-ruby) を使い、ファイルを並列に検査して
30
- 修正をアトミックに適用します。RuboCop 1.89 互換の CLI と JSON 出力により、既存のエディタや
31
- CI へ最小限の変更で導入できます。
37
+ 最新の Ruby 構文へ追従する [owayo/tree-sitter-ruby](https://github.com/owayo/tree-sitter-ruby) を使い、ファイルを並列に検査して修正をアトミックに適用します。RuboCop 1.89 互換の CLI と JSON 出力により、既存のエディタや CI へ最小限の変更で導入できます。
32
38
 
33
- ## 主な機能
39
+ ## 機能
34
40
 
35
- Bundler、Gemspec、Layout、Lint、Metrics、Migration、Naming、Security、Style の各デパートメントの
36
- Cop を実装しています。一覧はバイナリ自身が正本です。
41
+ Bundler、Gemspec、Layout、Lint、Metrics、Migration、Naming、Security、Style の各デパートメントの Cop を実装しています。一覧はバイナリ自身が正本です。
37
42
 
38
43
  ```bash
39
44
  # 認識済み Cop と実装状況の一覧
40
45
  sonicop --show-cops
41
46
  ```
42
47
 
43
- **RuboCop 1.89 の全 609 Cop を実装しています。** 本家のレジストリと名前まで一致しています。
44
- `Enabled: pending` の 159 個と `Enabled: false` の 56 個も含みます。この 215 個は本家でも
45
- 既定の実行では走らないので、`--only` で名指しするか設定で有効にしてください。本家に存在しない
46
- Cop 名を書いた場合だけエラーで止まります(`--ignore-unrecognized-cops` で続行できます)。
48
+ **RuboCop 1.89 の全 609 Cop を実装しています。** 本家のレジストリと名前まで一致しています。`Enabled: pending` の 159 個と `Enabled: false` の 56 個も含みます。この 215 個は本家でも既定の実行では走らないので、`--only` で名指しするか設定で有効にしてください。本家に存在しない Cop 名を書いた場合だけエラーで止まります(`--ignore-unrecognized-cops` で続行できます)。
47
49
 
48
50
  ### Cop 別の一致状況
49
51
 
50
- 全 609 Cop を両者で有効にし、本家の spec が供給する 37,491 ケースを、それぞれの spec が
51
- 指定した `TargetRubyVersion` で走らせて比較した結果です。**完全一致**とは、その Cop の offense が
52
- 位置・メッセージ・重大度・修正可否まで 1 件残らず一致し、どちらにも余りがないことを指します。
52
+ 全 609 Cop を両者で有効にし、本家の spec が供給する 37,491 ケースを、それぞれの spec が指定した `TargetRubyVersion` で走らせて比較した結果です。**完全一致**とは、その Cop の offense が位置・メッセージ・重大度・修正可否まで 1 件残らず一致し、どちらにも余りがないことを指します。
53
53
 
54
54
  <!-- conformance:start -->
55
55
  | デパートメント | Cop 数 | 検証済み | 完全一致 | 相違 |
@@ -66,62 +66,44 @@ Cop 名を書いた場合だけエラーで止まります(`--ignore-unrecogni
66
66
  | **合計** | **609** | **609** | **579** | **30** |
67
67
  <!-- conformance:end -->
68
68
 
69
- **先に読むべきは「検証済み」の列です。** ここで一度も発火しなかった Cop は、沈黙が一致と
70
- 見分けられないため、訊かれないまま一致に数えられてしまいます。**609 個すべてがここで発火します**。
71
- それが「完全一致」の列を意味あるものにしています。
72
-
73
- そのうち 3 個は専用の run を要しました。`Lint/DeprecatedReference`・`Lint/NameTypo`・
74
- `Lint/UnusedPrivateMethod` は `rubydex` のプロジェクトインデックスが無ければ何も報告せず、
75
- gem の導入と `AllCops/UseProjectIndex` の有効化が要ります。`Lint/DeprecatedReference` は
76
- さらに条件があり、`@deprecated` を持つメソッドの定義クラスを**継承したクラスの中**から
77
- 呼ぶ必要があります。本家の spec は「インデックス無しでは offense を出さない」という
78
- `expect_no_offenses` で始まるので「到達不能」と読みたくなりますが、そうではありません。
79
-
80
- 「完全一致」を 609 にし、「相違」をゼロにすることが現在の目標です。実コードを足しても
81
- 届きません。RuboCop が既定で無効にしている 56 Cop と、pending として出荷している Cop の多くは、
82
- ツリーがどれだけ大きくても素の実行では発火しないためです。そこに届くのは本家の spec が
83
- 供給する入力の方で、`tests/fixtures/upstream_spec_capture.jsonl` に記録したケースは
84
- **609 Cop すべてに到達します**(実測)。走らせ方には間違えやすい点が 2 つあり、
85
- **どちらも失敗せずに表を縮めます**。
86
-
87
- - **`TargetRubyVersion` は入力の一部であって、全体の設定ではありません。** 2.7 に固定すると
88
- `Style/ArrayIntersect`・`Naming/BlockForwarding`・`Style/ItBlockParameter` ほか 11 個が
89
- そもそも発火できません。各ケースはその spec が指定した版で走らせます。
90
- - **ファイル名そのものを見る Cop があります。** `Bundler/*` は `Gemfile`、`Gemspec/*` は
91
- `.gemspec`、`Naming/FileName` は名前自体を読みます。全ケースを `.rb` で書き出すと、
92
- この 17 Cop が何にも一致しませんでした。
93
-
94
- これとは別に、2026-08-29 に本家 Cop spec から直接抽出できた 11,506 ケースを oracle で
95
- 照合しました。本家が入力を読めなかった 226 ケースとクラッシュした 1 ケースを除き、測定できた
96
- 範囲では Sonicop の**検出差分・訂正差分ともにゼロ**でした。この結果は上の表には加えていません。
97
- 直接抽出できるケースが無い Cop が 51 個あり、ディレクティブ系 3 Cop は `--only` では測定不能な
98
- ため、この掃引だけで全 609 Cop の完全一致を証明できないからです。また、この直接掃引は各例の
99
- `TargetRubyVersion` をすべて再現せず、中立的な既定条件で実行しています。そのため、今回変更した
100
- Ruby 3.4 依存の 4 Cop は 3.4 で別途比較し、メッセージと位置が本家に完全一致することを確認しました。
101
-
102
- 設定値については別に測っています。既定値でしか一致しない Cop は半分しか実装していないのと
103
- 同じだからです。`Enforced*` 系の設定を持つ 111 Cop すべてを**既定以外の値**に倒して同じコーパスを
104
- 流すと、**622,317 件の offense のうち 99.995% が一致**し、発火した 96 Cop のうち 85 個が完全一致です。
105
- 残るのは 10 Cop(いずれも 17 件以下)と、本家がクラッシュして sonicop が正常に検出する 1 Cop です。
106
- 内訳は [CONFORMANCE.md](CONFORMANCE.md) にあります。
107
-
108
- どちらの表も `scripts/conformance_table.rb` で再現できます。
69
+ **先に読むべきは「検証済み」の列です。** ここで一度も発火しなかった Cop は、沈黙が一致と見分けられないため、訊かれないまま一致に数えられてしまいます。**609 個すべてがここで発火します**。それが「完全一致」の列を意味あるものにしています。
70
+
71
+ すべての Cop に届かせる方法、本家 spec から直接抽出したケースでの照合、既定以外の設定値の計測は [docs/cop-conformance.ja.md](docs/cop-conformance.ja.md) にまとめています。
109
72
 
110
73
  ## インストール
111
74
 
75
+ <!-- standard:install:start -->
76
+ ### Cargo
77
+
78
+ Rust 1.98 以上が必要です。
79
+
80
+ ```bash
81
+ cargo install --git https://github.com/owayo/sonicop --locked
82
+ ```
83
+
84
+ ### RubyGems
85
+
112
86
  ```bash
113
87
  gem install sonicop
114
88
  ```
115
89
 
116
- Linux、macOS、Windows 向けの platform gem にはネイティブ実行ファイルが含まれます。
117
- 対応する platform gem がない環境では、source gem がインストール時に Cargo でビルドします。
90
+ ### ソースから
118
91
 
119
- 最新版のソースから直接インストールすることもできます。
92
+ [mise](https://mise.jdx.dev/) が必要です (Rust のツールチェーンは `mise.toml` で固定しています)。
120
93
 
121
94
  ```bash
122
- cargo install --git https://github.com/owayo/sonicop
95
+ git clone https://github.com/owayo/sonicop.git
96
+ cd sonicop
97
+ make install
123
98
  ```
124
99
 
100
+ `make install` は `/usr/local/bin` に入れます。場所を変えるときは `INSTALL_PATH` を指定します (例: `make install INSTALL_PATH="$HOME/.local/bin"`)。
101
+ <!-- standard:install:end -->
102
+
103
+ ### プラットフォーム別の gem
104
+
105
+ [RubyGems](https://rubygems.org/gems/sonicop) で配っている Linux、macOS、Windows 向けの platform gem には、ネイティブ実行ファイルが含まれます。対応する platform gem がない環境では、source gem がインストール時に Cargo でビルドします。
106
+
125
107
  ## 使い方
126
108
 
127
109
  ```bash
@@ -138,27 +120,26 @@ sonicop -A
138
120
  # RuboCop 互換形状の JSON
139
121
  sonicop --format json
140
122
 
123
+ # エディタからの入力
124
+ printf '%s\n' 'value=10000' | sonicop --stdin example.rb --format json
125
+
141
126
  # 認識済み Cop と実装状況
142
127
  sonicop --show-cops
143
128
  ```
144
129
 
145
- 主な互換オプションは `-l`、`-x`、`--only`、`--except`、`-s/--stdin`、
146
- `-P/--parallel`、`-f/--format`、`-a/--autocorrect`、`-A/--autocorrect-all`、
147
- `-L/--list-target-files`、`-c/--config`、`-v/--version`、`-V/--verbose-version` です。
130
+ 主な互換オプションは `-l`、`-x`、`--only`、`--except`、`-s/--stdin`、`-P/--parallel`、`-f/--format`、`-a/--autocorrect`、`-A/--autocorrect-all`、`-L/--list-target-files`、`-c/--config`、`-v/--version`、`-V/--verbose-version` です。
148
131
 
149
- ### 設定
132
+ ## 設定
150
133
 
151
- 対象ファイルごとに `.rubocop.yml` を解決するため、1 回の実行でもサブディレクトリ設定が
152
- 適用されます。ローカル/HTTPS の `inherit_from`、`inherit_gem`、`inherit_mode`、
153
- `AllCops/DisabledByDefault`、`Include`/`Exclude`、Cop ごとの `Enabled`、`Exclude`、
154
- `Severity`、`Safe`、`SafeAutoCorrect` と設定値に対応します。宣言されたプラグイン由来の
155
- Cop は「認識済み・未実装」として受理し、Ruby プラグインコード自体は実行しません。
156
- リモート設定のリクエストには 30 秒のネットワークタイムアウトを設け、応答は 1 件あたり
157
- 5 MiB に制限します。
134
+ 対象ファイルごとに `.rubocop.yml` を解決するため、1 回の実行でもサブディレクトリ設定が適用されます。ローカル/HTTPS の `inherit_from`、`inherit_gem`、`inherit_mode`、`AllCops/DisabledByDefault`、`Include`/`Exclude`、Cop ごとの `Enabled`、`Exclude`、`Severity`、`Safe`、`SafeAutoCorrect` と設定値に対応します。宣言されたプラグイン由来の Cop は「認識済み・未実装」として受理し、Ruby プラグインコード自体は実行しません。リモート設定のリクエストには 30 秒のネットワークタイムアウトを設け、応答は 1 件あたり 5 MiB に制限します。
158
135
 
159
136
  ```yaml
160
137
  inherit_from: .rubocop_todo.yml
161
138
 
139
+ AllCops:
140
+ Exclude:
141
+ - "vendor/**/*"
142
+
162
143
  Layout/LineLength:
163
144
  Max: 100
164
145
 
@@ -166,70 +147,33 @@ Style/StringLiterals:
166
147
  EnforcedStyle: single_quotes
167
148
  ```
168
149
 
169
- 既存コマンドとの互換性を保つため、server/LSP/MCP、plugin 系の引数も受理します。
170
- サーバートランスポート、Ruby プラグイン実行、カスタム Cop、実装済み以外の Cop は実行しません。
171
- これらはその旨を出力します。`--server` / `--no-server` / `--lsp` / `--mcp` / `--plugin` は
172
- stderr に 1 行の注記を出します。
150
+ 既存コマンドとの互換性を保つため、server/LSP/MCP、plugin 系の引数も受理します。サーバートランスポート、Ruby プラグイン実行、カスタム Cop、実装済み以外の Cop は実行しません。これらはその旨を出力します。`--server` / `--no-server` / `--lsp` / `--mcp` / `--plugin` は stderr に 1 行の注記を出します。
173
151
 
174
- cache 系の引数は、受理するだけでなく実際に効きます。sonicop は独自の結果キャッシュを持ち、
175
- 検査時からサイズ・更新時刻・パーミッションのいずれも動いていないファイルには、
176
- 保存済みのレポートをそのまま返します。
152
+ cache 系の引数は、受理するだけでなく実際に効きます。sonicop は独自の結果キャッシュを持ち、検査時からサイズ・更新時刻・パーミッションのいずれも動いていないファイルには、保存済みのレポートをそのまま返します。
177
153
 
178
- - キャッシュは既定で有効です。`--cache false` で無効化できます。設定ファイルの
179
- `AllCops/MaxFilesInCache: 0` でも同じです。
180
- - 置き場所は `--cache-root DIR` で指定します。省略時は `$XDG_CACHE_HOME/sonicop`、
181
- macOS では `~/Library/Caches/sonicop`、それ以外は `~/.cache/sonicop` です。
182
- `--cache-root` は `--cache false` とは併用できません。
154
+ - キャッシュは既定で有効です。`--cache false` で無効化できます。設定ファイルの `AllCops/MaxFilesInCache: 0` でも同じです。
155
+ - 置き場所は `--cache-root DIR` で指定します。省略時は `$XDG_CACHE_HOME/sonicop`、macOS では `~/Library/Caches/sonicop`、それ以外は `~/.cache/sonicop` です。`--cache-root` は `--cache false` とは併用できません。
183
156
  - 保持するレポート数の上限は `AllCops/MaxFilesInCache` で、既定は本家と同じ 20,000 件です。
184
157
  - autocorrect 実行、`--stdin`、`--profile`、`--memory` では読み書きしません。
185
- - 本家のキャッシュとは共有しません。形式が別物であり、書いたときとまったく同じ
186
- ビルドの sonicop にしかエントリを返さないためです。
187
-
188
- 無言なのは Cop の設定値のほうです。sonicop が実装していない設定値は**警告なしに無視されます**。
189
- 名前を綴り間違えた設定値も同様です。つまり
190
- **offense が 0 件であることは、その設定が効いた証拠にはなりません**。
191
- 無視された設定値と、違反の無いファイルが、同じ出力になるためです。
192
- どの設定値まで検証済みかは上の *Cop 別の一致状況* を参照してください。
193
-
194
- Cop の*名前*は検査されます。設定ファイルに未知の Cop 名があれば、実行はエラーで止まります。
195
- 素通りするのは、既知の Cop の中の設定値です。
196
-
197
- ### 適合性
198
-
199
- 実装済み Cop は、RuboCop 自身・Rails・Ruby・Homebrew・Mastodon の 5 プロジェクト
200
- 計 18,251 ファイルに対して、両者とも本家既定設定で検証しています。offense は Cop 名・パス・
201
- 行・桁・終端行・終端桁・長さ・メッセージ・重大度・修正可否のすべてで突き合わせます。
202
-
203
- 5 つのうち 3 つが**完全一致**です。RuboCop 自身のツリー(5,766 件)、Rails(167,760 件)、
204
- Mastodon(15,286 件)で、過剰も不足もメタデータ差もありません。対象ファイル一覧は 5 つすべてで
205
- **件数だけでなくパスまで**一致します(集合として比較。どの側にも余りはありません)。
206
- 残る差分は `Lint/Syntax` に集中しています。その大半は、本家の LALR パーサが
207
- 構文エラーから回復して出す追加診断を tree-sitter では再現できないことによるもので、
208
- 診断位置の差は不足と過剰の両方に出ます。Homebrew の不足 997 件・過剰 263 件はすべて
209
- `Lint/Syntax` ですが、**構文エラーと判定したファイル集合は 569 対 569 で完全一致**し、
210
- 移植版だけが拒否したファイルは 0 件です。過剰 263 件は共有エラーファイル 135 件にあり、
211
- すべて同じファイル内の共通診断より後ろにあるため、別の受理判定バグではなく回復後の診断位置差です。
212
- Homebrew を問題の構文をサポートする Ruby 3.1 として測ると、両者とも `Lint/Syntax` は 0 件になります。
213
- autocorrect は RuboCop 自身のツリーと Mastodon でバイト単位に一致します。この 2 つは死守ラインと
214
- して扱い、バイト一致が崩れた場合は既知差分ではなく退行として直します。
215
-
216
- コマンド、この数値を測ったコーパスのコミット、この種の計測が誤った結論を導く 2 つの罠は
217
- [CONFORMANCE.md](CONFORMANCE.md) にまとめています。
218
-
219
- ### 性能
220
-
221
- 適合性検証に使う 5 コーパスすべてで測定しました。両者とも同梱の既定設定
222
- (`--force-default-config`)で走らせているためプロジェクト側の `.rubocop.yml` は読まず、
223
- どのコーパスでも**対象ファイル数は一致**しています。この計測に必要なのはそこまでで、
224
- どちらの側も少なく検査してはいない、と言えます。パス単位の一致はより強い主張で、
225
- 上の*適合性*の節で 5 つすべてについて示していますが、それは固定したリビジョンでの測定であって
226
- この速度計測の run そのものではありません。
227
-
228
- 両者とも既定の全 Cop で走らせています。**同じ 394 Cop** が名前まで一致しているため、
229
- どちらも絞る必要がなく、素の実行がそのまま対等な比較になります。
230
- (394 は 609 から `Enabled: pending` の 159 個と `Enabled: false` の 56 個を除いた残りで、
231
- 既定の実行はどちらの群にも届きません。)
232
- 各値は暖機後 2 回の最速値です。
158
+ - 本家のキャッシュとは共有しません。形式が別物であり、書いたときとまったく同じビルドの sonicop にしかエントリを返さないためです。
159
+
160
+ 無言なのは Cop の設定値のほうです。sonicop が実装していない設定値は**警告なしに無視されます**。名前を綴り間違えた設定値も同様です。つまり **offense が 0 件であることは、その設定が効いた証拠にはなりません**。無視された設定値と、違反の無いファイルが、同じ出力になるためです。どの設定値まで検証済みかは [既定以外の設定値](docs/cop-conformance.ja.md#既定以外の設定値) を参照してください。
161
+
162
+ Cop の*名前*は検査されます。設定ファイルに未知の Cop 名があれば、実行はエラーで止まります。素通りするのは、既知の Cop の中の設定値です。
163
+
164
+ ## 適合性
165
+
166
+ 実装済み Cop は、RuboCop 自身・Rails・Ruby・Homebrew・Mastodon の 5 プロジェクト計 18,251 ファイルに対して、両者とも本家既定設定で検証しています。offense は Cop 名・パス・行・桁・終端行・終端桁・長さ・メッセージ・重大度・修正可否のすべてで突き合わせます。
167
+
168
+ 5 つのうち 3 つが**完全一致**です。RuboCop 自身のツリー(5,766 件)、Rails(167,760 件)、Mastodon(15,286 件)で、過剰も不足もメタデータ差もありません。対象ファイル一覧は 5 つすべてで**件数だけでなくパスまで**一致します(集合として比較。どの側にも余りはありません)。残る差分は `Lint/Syntax` に集中しています。その大半は、本家の LALR パーサが構文エラーから回復して出す追加診断を tree-sitter では再現できないことによるもので、診断位置の差は不足と過剰の両方に出ます。Homebrew の不足 997 件・過剰 263 件はすべて `Lint/Syntax` ですが、**構文エラーと判定したファイル集合は 569 対 569 で完全一致**し、移植版だけが拒否したファイルは 0 件です。過剰 263 件は共有エラーファイル 135 件にあり、すべて同じファイル内の共通診断より後ろにあるため、別の受理判定バグではなく回復後の診断位置差です。Homebrew を問題の構文をサポートする Ruby 3.1 として測ると、両者とも `Lint/Syntax` は 0 件になります。autocorrect は RuboCop 自身のツリーと Mastodon でバイト単位に一致します。この 2 つは死守ラインとして扱い、バイト一致が崩れた場合は既知差分ではなく退行として直します。
169
+
170
+ コマンド、この数値を測ったコーパスのコミット、この種の計測が誤った結論を導く 2 つの罠は [CONFORMANCE.md](CONFORMANCE.md) にまとめています。
171
+
172
+ ## 性能
173
+
174
+ 適合性検証に使う 5 コーパスすべてで測定しました。両者とも同梱の既定設定(`--force-default-config`)で走らせているためプロジェクト側の `.rubocop.yml` は読まず、どのコーパスでも**対象ファイル数は一致**しています。この計測に必要なのはそこまでで、どちらの側も少なく検査してはいない、と言えます。パス単位の一致はより強い主張で、上の[適合性](#適合性)の節で 5 つすべてについて示していますが、それは固定したリビジョンでの測定であってこの速度計測の run そのものではありません。
175
+
176
+ 両者とも既定の全 Cop で走らせています。**同じ 394 Cop** が名前まで一致しているため、どちらも絞る必要がなく、素の実行がそのまま対等な比較になります。(394 は 609 から `Enabled: pending` の 159 個と `Enabled: false` の 56 個を除いた残りで、既定の実行はどちらの群にも届きません。)各値は暖機後 2 回の最速値です。
233
177
 
234
178
  | コーパス | ファイル | offense | RuboCop 並列 | Sonicop 並列 | RuboCop 単一 | Sonicop 単一 |
235
179
  |---|---:|---:|---:|---:|---:|---:|
@@ -239,30 +183,11 @@ autocorrect は RuboCop 自身のツリーと Mastodon でバイト単位に一
239
183
  | rails/rails | 3,562 | 168,615 | 32.90 秒 | **8.84 秒** | 85.71 秒 | **19.17 秒** |
240
184
  | ruby/ruby | 7,477 | 765,975 | 86.89 秒 | **15.59 秒** | 193.59 秒 | **37.98 秒** |
241
185
 
242
- 差は並列で 3.7〜7.4 倍、単一プロセスで 4.5〜6.2 倍と幅があり、1 コーパスでは代表できません。
243
- **単一プロセスの列を読み、並列は目安として扱ってください。** 同じ 2 つのバイナリを 1 日に
244
- 3 回測ったところ、単一プロセスの値は毎回 16% 以内に収まったのに対し、RuboCop 自身のツリーでの
245
- 並列の倍率は、マシンが他に何をしていたかだけで 3.3 倍から 9.2 倍まで動きました。単一プロセスは
246
- エンジンを測っていますが、並列はエンジンに加えて「その実行でスケジューリングがそのツリーに
247
- どれだけ噛み合ったか」を測っています。
248
-
249
- 仕事を省いて速いわけではありません。この同じ 394 Cop について、表のどのコーパスでも
250
- **offense の総数が一致**し、RuboCop 自身のツリーと Mastodon ではその 1 件ずつが同じ位置・
251
- 同じメッセージ・同じ severity です。Rails は 168,615 件のうち 2 件だけ食い違います
252
- (Sonicop が出す `Style/CaseLikeIf` 1 件と、出さない `Metrics/AbcSize` 1 件)。
253
- RuboCop 自身のツリーでは 1 件の `correctable` フラグが違います。
254
- autocorrect は前者と後者でバイト単位に一致します。
255
-
256
- 再現時に注意が必要な点が 4 つあります。RuboCop は **`--cache false` と併用すると
257
- `--parallel` を黙って無効化します**。そのためここでの並列実行はキャッシュを有効にしたうえで
258
- 実行ごとにキャッシュディレクトリを消しており、`--cache false --parallel` で計測すると
259
- 単一プロセスを測ることになり差が過大に出ます。また RuboCop の既定は単一プロセス、
260
- Sonicop は `--no-parallel` を渡さない限り並列です。そして**両方ともキャッシュを空にする**
261
- 必要があります。Sonicop も既定でキャッシュするため、同じツリーを 2 回目に流すと自分の
262
- キャッシュが答えてしまい、エンジンについては何も測れません。どちらにも使い捨ての
263
- キャッシュディレクトリを渡してください。最後に、そのキャッシュディレクトリは**実パス**である
264
- 必要があります。macOS の `mktemp -d` は `/var/folders/…` を返し、その `/var` は symlink なので
265
- RuboCop はそこを拒み、キャッシュ無しで走ってしまいます。
186
+ 差は並列で 3.7〜7.4 倍、単一プロセスで 4.5〜6.2 倍と幅があり、1 コーパスでは代表できません。**単一プロセスの列を読み、並列は目安として扱ってください。** 同じ 2 つのバイナリを 1 日に 3 回測ったところ、単一プロセスの値は毎回 16% 以内に収まったのに対し、RuboCop 自身のツリーでの並列の倍率は、マシンが他に何をしていたかだけで 3.3 倍から 9.2 倍まで動きました。単一プロセスはエンジンを測っていますが、並列はエンジンに加えて「その実行でスケジューリングがそのツリーにどれだけ噛み合ったか」を測っています。
187
+
188
+ 仕事を省いて速いわけではありません。この同じ 394 Cop について、表のどのコーパスでも **offense の総数が一致**し、RuboCop 自身のツリーと Mastodon ではその 1 件ずつが同じ位置・同じメッセージ・同じ severity です。Rails は 168,615 件のうち 2 件だけ食い違います(Sonicop が出す `Style/CaseLikeIf` 1 件と、出さない `Metrics/AbcSize` 1 件)。RuboCop 自身のツリーでは 1 件の `correctable` フラグが違います。autocorrect は前者と後者でバイト単位に一致します。
189
+
190
+ 再現時に注意が必要な点が 4 つあります。RuboCop は **`--cache false` と併用すると `--parallel` を黙って無効化します**。そのためここでの並列実行はキャッシュを有効にしたうえで実行ごとにキャッシュディレクトリを消しており、`--cache false --parallel` で計測すると単一プロセスを測ることになり差が過大に出ます。また RuboCop の既定は単一プロセス、Sonicop は `--no-parallel` を渡さない限り並列です。そして**両方ともキャッシュを空にする**必要があります。Sonicop も既定でキャッシュするため、同じツリーを 2 回目に流すと自分のキャッシュが答えてしまい、エンジンについては何も測れません。どちらにも使い捨てのキャッシュディレクトリを渡してください。最後に、そのキャッシュディレクトリは**実パス**である必要があります。macOS の `mktemp -d` は `/var/folders/…` を返し、その `/var` は symlink なので RuboCop はそこを拒み、キャッシュ無しで走ってしまいます。
266
191
 
267
192
  ```bash
268
193
  # RuboCop(並列・キャッシュは毎回空・既定の全 394 Cop)
@@ -274,69 +199,45 @@ rubocop --force-default-config --cache true --cache-root "$root" \
274
199
  sonicop --force-default-config --cache-root "$root" --format quiet
275
200
  ```
276
201
 
277
- キャッシュの書き込みもこの数値に含まれており、無料ではありません。索引は全 offense を
278
- 「見つかった行のテキスト」付きで保持するため、`ruby/ruby` では 336 MB になります。
279
- 2 回目の実行(キャッシュ命中)は `ruby/ruby` で 1.59 秒、Rails で 0.42 秒です。
280
-
281
- 測定機は Apple M2(8 コア)、Ruby 4.0.6(YJIT 利用可)、RubyGems 導入の RuboCop 1.89.0。
282
- 2026-08-31 に、`rubocop_rubocop` 2693129 / `mastodon_mastodon` b59ddc7 / `Homebrew_brew` b42173b /
283
- `rails_rails` a19f07f / `ruby_ruby` 22e4a75 の各リビジョンに対して測定しました。
284
- 1 分平均のロードアベレージは各行の測定時点で 4.5〜7.1 で、その大半は RuboCop 自身の並列
285
- ワーカーです(計測する以上避けられません)。**アイドル状態ではありません。**
286
- 各コーパスで両者を連続して同じ条件で測っているため倍率は保たれますが、秒数そのものは下限ではなく、
287
- 静かなマシンならより速く出ます。コアを奪い合うものが動いていると両者とも膨らみ、その度合いは
288
- 一致しません。それが並列の列があれだけ動く理由です。秒数そのものが重要なときは、他に負荷のない
289
- 状態で測り、**実行の前後でロードアベレージを記録してください** — その情報が無い数値は、
290
- 別の数値と比べられません。
202
+ キャッシュの書き込みもこの数値に含まれており、無料ではありません。索引は全 offense を「見つかった行のテキスト」付きで保持するため、`ruby/ruby` では 336 MB になります。2 回目の実行(キャッシュ命中)は `ruby/ruby` で 1.59 秒、Rails で 0.42 秒です。
203
+
204
+ 測定機は Apple M2(8 コア)、Ruby 4.0.6(YJIT 利用可)、RubyGems 導入の RuboCop 1.89.0。2026-08-31 に、`rubocop_rubocop` 2693129 / `mastodon_mastodon` b59ddc7 / `Homebrew_brew` b42173b / `rails_rails` a19f07f / `ruby_ruby` 22e4a75 の各リビジョンに対して測定しました。1 分平均のロードアベレージは各行の測定時点で 4.5〜7.1 で、その大半は RuboCop 自身の並列ワーカーです(計測する以上避けられません)。**アイドル状態ではありません。** 各コーパスで両者を連続して同じ条件で測っているため倍率は保たれますが、秒数そのものは下限ではなく、静かなマシンならより速く出ます。コアを奪い合うものが動いていると両者とも膨らみ、その度合いは一致しません。それが並列の列があれだけ動く理由です。秒数そのものが重要なときは、他に負荷のない状態で測り、**実行の前後でロードアベレージを記録してください** — その情報が無い数値は、別の数値と比べられません。
291
205
 
292
206
  ## 開発
293
207
 
294
- 入口は `make` に一本化しています。`make help` で全ターゲットを確認できます。gem 配布の
295
- タスクは Rakefile 側にあり、`make` から呼び出します。
208
+ <!-- standard:dev:start -->
209
+ [mise](https://mise.jdx.dev/) が必要です。ツールの版は `mise.toml` で固定しています。
296
210
 
297
211
  ```bash
298
- make build # デバッグビルド
299
- make check # fmt、clippy、Rust テスト、Ruby ラッパーテスト、バージョン整合
300
- make gem # source gem
212
+ make setup # ツールチェーン (mise) と依存を取得する
213
+ make ci # CI と同じ検査 (書き換えない)
301
214
  ```
302
215
 
303
- ### Cop の追加
304
-
305
- Cop は `src/rules/<デパートメント>/<cop>.rs` の 1 ファイルで、公開するのは
306
- `check(context, offenses)` の 1 関数だけです。登録はデパートメントの `mod.rs` に 1 行を足します。
307
-
308
- ```rust
309
- department_rules! {
310
- "Layout";
311
- line_length => ("LineLength", Convention),
312
- }
313
- ```
314
-
315
- Cop 名と既定 severity を書くのはこの 1 行だけです。Cop 本体では名前は暗黙で、
316
- `context.setting("Max")` が `Layout/LineLength: Max` を読み、`context.offense(message, range)` が
317
- その Cop の名前と設定済み severity で報告します。Cop が自分の名前を 2 度書ける設計では、
318
- レジストリと食い違っても型検査では捕まりません。
319
-
320
- 全ノード走査より `context.nodes_of("kind")` を優先してください。Cop は全ファイルに対して走るため、
321
- Cop ごとの全走査はファイル規模ではなく Cop 数に比例して重くなります。
322
-
323
- バージョンの正本は `Cargo.toml` です。`lib/sonicop/version.rb` は `make version-sync` で
324
- 生成してコミットします(gemspec がパッケージ時に読むため)。両者が食い違うと CI が落ちます。
325
-
326
- `config/default.yml` は上流 RuboCop から取り込んだものです。再取得は
327
- `scripts/sync_default_yml.sh <rubocop-version>` で行い、由来のバージョンがファイル先頭に
328
- 記録されます。
329
-
330
- `src/display_width_table.rs` も生成物で、コミットします。RuboCop は表示桁を
331
- `unicode-display_width` gem で数えるため、この表は手書きせず gem から生成しています。
332
- 手書きの例外表は実際にずれており、NFD 分解された日本語でキャレットの本数が合わなくなっていました。
333
- 再生成は `ruby scripts/dump_display_width.rb > src/display_width_table.rs` で行い、
334
- gem と Unicode のバージョンがファイル先頭に記録されます。
335
-
336
- 依存更新には `depup --install` を使います。Ruby grammar は再現可能性のため `Cargo.toml` で
337
- fork のコミットを固定しています。
216
+ | コマンド | 説明 |
217
+ |---|---|
218
+ | `make setup` | ツールチェーン (mise) と依存を取得する |
219
+ | `make build` | デバッグ版をビルドする |
220
+ | `make release` | リリース版をビルドする |
221
+ | `make run` | デバッグ版を実行する (引数は ARGS="...") |
222
+ | `make test` | テストを実行する |
223
+ | `make lint` | clippy を警告ゼロで通す |
224
+ | `make fmt` | コードを整形する (書き換える) |
225
+ | `make fmt-check` | 整形済みかを確かめる (書き換えない) |
226
+ | `make check` | 整形と静的検査 (書き換えない) |
227
+ | `make ci` | CI と同じ検査 (書き換えない) |
228
+ | `make install` | リリース版を INSTALL_PATH (既定 /usr/local/bin) に入れる |
229
+ | `make uninstall` | INSTALL_PATH から取り除く |
230
+ | `make clean` | ビルド成果物を消す |
231
+
232
+ `make` でターゲットの一覧を表示します。リリースは GitHub Actions で行います (**Actions → Release → Run workflow**)。
233
+ <!-- standard:dev:end -->
234
+
235
+ Rakefile には gem の組み立てと版の処理だけがあり、`make gem`・`make gem-platform`・`make version-sync` がそれを呼びます。版の正本は `Cargo.toml` です。Cop の追加、上流から取り込むファイル、ほかのターゲットは [docs/development.ja.md](docs/development.ja.md) にまとめています。
338
236
 
339
237
  ## ライセンス
340
238
 
341
- [MIT](LICENSE)。同梱する RuboCop 既定設定とパーサー依存の著作権表示は
342
- [NOTICE](NOTICE) および [`licenses/`](licenses/) に収録しています。
239
+ <!-- standard:license:start -->
240
+ [MIT](LICENSE)
241
+ <!-- standard:license:end -->
242
+
243
+ 同梱する RuboCop 既定設定とパーサー依存の著作権表示は [NOTICE](NOTICE) および [`licenses/`](licenses/) に収録しています。
data/README.md CHANGED
@@ -1,57 +1,55 @@
1
- <h1 align="center">
2
- <img src="docs/images/sonicop_logo_header.png" width="600" alt="Sonicop">
3
- </h1>
1
+ <p align="center">
2
+ <img src="docs/images/sonicop_logo_header.png" width="320" alt="sonicop">
3
+ </p>
4
+
5
+ <h1 align="center">sonicop</h1>
6
+
7
+ <p align="center">
8
+ A fast, native RuboCop-compatible Ruby linter and formatter written in Rust
9
+ </p>
10
+
11
+ <!-- standard:badges:start -->
12
+ <h3 align="center">Supported Platforms</h3>
4
13
 
5
14
  <p align="center">
6
- <strong>A fast, native RuboCop-compatible Ruby linter and formatter written in Rust.</strong>
15
+ <img src="https://img.shields.io/badge/Linux-FCC624?logo=linux&amp;logoColor=black" alt="Linux">
16
+ <img src="https://img.shields.io/badge/macOS-000000?logo=apple&amp;logoColor=white" alt="macOS">
17
+ <img src="https://img.shields.io/badge/Windows-0078D6" alt="Windows">
7
18
  </p>
8
19
 
9
20
  <p align="center">
10
- <a href="https://github.com/owayo/sonicop/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/owayo/sonicop/actions/workflows/ci.yml/badge.svg?branch=main"></a>
11
- <a href="https://rubygems.org/gems/sonicop"><img alt="Gem Version" src="https://img.shields.io/gem/v/sonicop"></a>
12
- <a href="LICENSE"><img alt="License" src="https://img.shields.io/github/license/owayo/sonicop"></a>
21
+ <a href="https://github.com/owayo/sonicop/actions/workflows/ci.yml"><img src="https://github.com/owayo/sonicop/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
22
+ <a href="https://github.com/owayo/sonicop/releases/latest"><img src="https://img.shields.io/github/v/release/owayo/sonicop" alt="Release"></a>
23
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/owayo/sonicop" alt="License"></a>
24
+ <a href="https://rubygems.org/gems/sonicop"><img src="https://img.shields.io/gem/v/sonicop" alt="RubyGems"></a>
13
25
  </p>
14
26
 
15
27
  <p align="center">
16
28
  <a href="README.md">English</a> |
17
29
  <a href="README.ja.md">日本語</a>
18
30
  </p>
31
+ <!-- standard:badges:end -->
19
32
 
20
33
  ---
21
34
 
22
- ## Overview
23
-
24
- Sonicop is a fast Ruby linter and formatter that runs as a native executable without starting a
25
- Ruby process. Existing `.rubocop.yml` files work as-is, including nested configuration,
26
- inheritance, file inclusion and exclusion, severity, and autocorrect settings.
35
+ Sonicop is a fast Ruby linter and formatter that runs as a native executable without starting a Ruby process. Existing `.rubocop.yml` files work as-is, including nested configuration, inheritance, file inclusion and exclusion, severity, and autocorrect settings.
27
36
 
28
- It uses the actively maintained
29
- [owayo/tree-sitter-ruby](https://github.com/owayo/tree-sitter-ruby) grammar, inspects files in
30
- parallel, and applies corrections atomically. Its RuboCop 1.89-compatible CLI and JSON output fit
31
- existing editor and CI integrations with minimal changes.
37
+ It uses the actively maintained [owayo/tree-sitter-ruby](https://github.com/owayo/tree-sitter-ruby) grammar, inspects files in parallel, and applies corrections atomically. Its RuboCop 1.89-compatible CLI and JSON output fit existing editor and CI integrations with minimal changes.
32
38
 
33
39
  ## Features
34
40
 
35
- Sonicop implements cops in the Bundler, Gemspec, Layout, Lint, Metrics, Migration, Naming,
36
- Security, and Style departments. The binary itself is the authoritative list:
41
+ Sonicop implements cops in the Bundler, Gemspec, Layout, Lint, Metrics, Migration, Naming, Security, and Style departments. The binary itself is the authoritative list:
37
42
 
38
43
  ```bash
39
44
  # Every recognized cop and its implementation status
40
45
  sonicop --show-cops
41
46
  ```
42
47
 
43
- **All 609 RuboCop 1.89 cops are implemented**, matched name for name against the upstream registry.
44
- That includes the 159 shipped as `Enabled: pending` and the 56 shipped as `Enabled: false`, which a
45
- default run does not reach on either side — name them with `--only` or switch them on in a
46
- configuration, exactly as with RuboCop. Unknown cop names still fail validation unless
47
- `--ignore-unrecognized-cops` is supplied.
48
+ **All 609 RuboCop 1.89 cops are implemented**, matched name for name against the upstream registry. That includes the 159 shipped as `Enabled: pending` and the 56 shipped as `Enabled: false`, which a default run does not reach on either side — name them with `--only` or switch them on in a configuration, exactly as with RuboCop. Unknown cop names still fail validation unless `--ignore-unrecognized-cops` is supplied.
48
49
 
49
50
  ### Cop conformance
50
51
 
51
- All 609 cops switched on, on both sides, over the 37,491 cases RuboCop's own specs supply, each
52
- run at the `TargetRubyVersion` its spec asked for. A cop counts as an **exact match** only when its
53
- offenses agree completely: every position, message, severity and correctable flag, with nothing
54
- extra on either side.
52
+ All 609 cops switched on, on both sides, over the 37,491 cases RuboCop's own specs supply, each run at the `TargetRubyVersion` its spec asked for. A cop counts as an **exact match** only when its offenses agree completely: every position, message, severity and correctable flag, with nothing extra on either side.
55
53
 
56
54
  <!-- conformance:start -->
57
55
  | Department | Cops | Exercised | Exact match | Diverging |
@@ -68,64 +66,44 @@ extra on either side.
68
66
  | **Total** | **609** | **609** | **579** | **30** |
69
67
  <!-- conformance:end -->
70
68
 
71
- **Read the *Exercised* column first.** A cop nothing here made fire contributes neither way — its
72
- silence is indistinguishable from agreement, so it would be counted as agreement without ever
73
- being asked. **Every one of the 609 fires here**, which is what makes the *Exact match* column
74
- mean what it says.
75
-
76
- Three of them took a run of their own. `Lint/DeprecatedReference`, `Lint/NameTypo` and
77
- `Lint/UnusedPrivateMethod` report nothing without a `rubydex` project index, which needs the gem
78
- installed and `AllCops/UseProjectIndex` switched on — and `Lint/DeprecatedReference` needs more
79
- than that: the call has to sit inside a class inheriting the one whose method carries the
80
- `@deprecated` tag. Upstream's own specs open with an `expect_no_offenses` saying the cop is silent
81
- without the index, which is easy to read as "unreachable"; it is not.
82
-
83
- Getting *Exact match* to 609 while reducing *Diverging* to zero is the current goal. More real Ruby
84
- does not get there: the 56 cops RuboCop ships disabled, and much of what it ships as pending, never
85
- fire in a plain run however large the tree. What does reach every one of them is the input its own
86
- specs supply — the cases recorded in `tests/fixtures/upstream_spec_capture.jsonl` touch **609 of 609
87
- cops**, measured. Two things about running them are easy to get wrong, and both silently shrink the
88
- table rather than failing:
89
-
90
- - **`TargetRubyVersion` is part of the input, not a global.** Pinning everything at 2.7 leaves
91
- `Style/ArrayIntersect`, `Naming/BlockForwarding`, `Style/ItBlockParameter` and eleven others
92
- unable to fire at all. Each case is run at the version its spec asked for.
93
- - **The filename is what several cops inspect.** `Bundler/*` needs a `Gemfile`, `Gemspec/*` a
94
- `.gemspec`, and `Naming/FileName` reads the name itself. Writing every case as `.rb` had all 17
95
- of those cops matching nothing.
96
-
97
- A separate direct oracle sweep on 2026-08-29 examined 11,506 cases extractable from the upstream
98
- cop specs. RuboCop could not read 226 of those inputs and crashed on one; among the measurable
99
- cases, Sonicop had **zero detection differences and zero correction differences**. This result is
100
- not folded into the table above: 51 cops had no directly extractable case, and three directive cops
101
- cannot be measured under `--only`, so the sweep does not prove that all 609 cops are exact. The
102
- direct sweep also uses neutral/default conditions rather than preserving every example's
103
- `TargetRubyVersion`. The four Ruby 3.4-sensitive cops changed in this pass were therefore compared
104
- separately at 3.4, where their messages and locations matched RuboCop exactly.
105
-
106
- Configuration is measured separately, because a cop that only matches at its default value is half
107
- a cop. Every one of the 111 cops carrying an `Enforced*` setting was switched to a **non-default**
108
- value at once and the corpus re-run: **99.995% of 622,317 offenses match**, with 85 of the 96 cops
109
- that fired matching exactly. The residue is 10 cops of at most 17 offenses each, plus one where
110
- RuboCop crashes and sonicop does not; the list is in [CONFORMANCE.md](CONFORMANCE.md).
111
-
112
- Reproduce either table with `scripts/conformance_table.rb`.
69
+ **Read the *Exercised* column first.** A cop nothing here made fire contributes neither way — its silence is indistinguishable from agreement, so it would be counted as agreement without ever being asked. **Every one of the 609 fires here**, which is what makes the *Exact match* column mean what it says.
70
+
71
+ How every cop is reached, a separate direct oracle sweep over the upstream specs, and the measurement of non-default settings: [docs/cop-conformance.md](docs/cop-conformance.md).
113
72
 
114
73
  ## Installation
115
74
 
75
+ <!-- standard:install:start -->
76
+ ### Cargo
77
+
78
+ Requires Rust 1.98 or later.
79
+
80
+ ```bash
81
+ cargo install --git https://github.com/owayo/sonicop --locked
82
+ ```
83
+
84
+ ### RubyGems
85
+
116
86
  ```bash
117
87
  gem install sonicop
118
88
  ```
119
89
 
120
- Platform gems include native executables for Linux, macOS, and Windows. When a prebuilt platform
121
- gem is unavailable, the source gem builds the executable with Cargo during installation.
90
+ ### From Source
122
91
 
123
- You can also install the latest source directly:
92
+ Requires [mise](https://mise.jdx.dev/) (the Rust toolchain is pinned in `mise.toml`).
124
93
 
125
94
  ```bash
126
- cargo install --git https://github.com/owayo/sonicop
95
+ git clone https://github.com/owayo/sonicop.git
96
+ cd sonicop
97
+ make install
127
98
  ```
128
99
 
100
+ `make install` installs to `/usr/local/bin`. Set `INSTALL_PATH` to change it (for example `make install INSTALL_PATH="$HOME/.local/bin"`).
101
+ <!-- standard:install:end -->
102
+
103
+ ### Platform gems
104
+
105
+ Platform gems on [RubyGems](https://rubygems.org/gems/sonicop) include native executables for Linux, macOS, and Windows. When a prebuilt platform gem is unavailable, the source gem builds the executable with Cargo during installation.
106
+
129
107
  ## Usage
130
108
 
131
109
  ```bash
@@ -149,19 +127,11 @@ printf '%s\n' 'value=10000' | sonicop --stdin example.rb --format json
149
127
  sonicop --show-cops
150
128
  ```
151
129
 
152
- Key compatibility flags include `-l`, `-x`, `--only`, `--except`, `-s/--stdin`, `-P/--parallel`,
153
- `-f/--format`, `-a/--autocorrect`, `-A/--autocorrect-all`, `-L/--list-target-files`,
154
- `-c/--config`, `-v/--version`, and `-V/--verbose-version`.
130
+ Key compatibility flags include `-l`, `-x`, `--only`, `--except`, `-s/--stdin`, `-P/--parallel`, `-f/--format`, `-a/--autocorrect`, `-A/--autocorrect-all`, `-L/--list-target-files`, `-c/--config`, `-v/--version`, and `-V/--verbose-version`.
155
131
 
156
- ### Configuration
132
+ ## Configuration
157
133
 
158
- Sonicop resolves `.rubocop.yml` from each target file, so nested configurations apply within one
159
- run. Local and HTTPS `inherit_from`, `inherit_gem`, `inherit_mode`,
160
- `AllCops/DisabledByDefault`, `Include`, and `Exclude`, plus per-cop `Enabled`, `Exclude`,
161
- `Severity`, `Safe`, `SafeAutoCorrect`, and cop settings are supported. Cops supplied by declared
162
- plugins are accepted as recognized-but-unimplemented without executing Ruby plugin code.
163
- Remote configuration requests use 30-second network timeouts, and each response is limited to
164
- 5 MiB.
134
+ Sonicop resolves `.rubocop.yml` from each target file, so nested configurations apply within one run. Local and HTTPS `inherit_from`, `inherit_gem`, `inherit_mode`, `AllCops/DisabledByDefault`, `Include`, and `Exclude`, plus per-cop `Enabled`, `Exclude`, `Severity`, `Safe`, `SafeAutoCorrect`, and cop settings are supported. Cops supplied by declared plugins are accepted as recognized-but-unimplemented without executing Ruby plugin code. Remote configuration requests use 30-second network timeouts, and each response is limited to 5 MiB.
165
135
 
166
136
  ```yaml
167
137
  inherit_from: .rubocop_todo.yml
@@ -177,70 +147,33 @@ Style/StringLiterals:
177
147
  EnforcedStyle: single_quotes
178
148
  ```
179
149
 
180
- The CLI accepts RuboCop's server/LSP/MCP and plugin flags to keep existing command lines
181
- parse-compatible. Sonicop does not provide server transports, Ruby plugin execution, custom Ruby
182
- cops, or cops outside the implemented set. Each of those flags says so: `--server`, `--no-server`,
183
- `--lsp`, `--mcp`, and `--plugin` print a one-line notice on stderr.
150
+ The CLI accepts RuboCop's server/LSP/MCP and plugin flags to keep existing command lines parse-compatible. Sonicop does not provide server transports, Ruby plugin execution, custom Ruby cops, or cops outside the implemented set. Each of those flags says so: `--server`, `--no-server`, `--lsp`, `--mcp`, and `--plugin` print a one-line notice on stderr.
184
151
 
185
- The cache flags are honoured rather than merely parsed. Sonicop keeps a result cache of its own and
186
- serves a stored report for a file whose size, modification time and permission bits have not moved
187
- since it was inspected.
152
+ The cache flags are honoured rather than merely parsed. Sonicop keeps a result cache of its own and serves a stored report for a file whose size, modification time and permission bits have not moved since it was inspected.
188
153
 
189
- - Caching is on by default. `--cache false` turns it off, as does `AllCops/MaxFilesInCache: 0` in a
190
- configuration file.
191
- - `--cache-root DIR` chooses where it lives. Without it the root is `$XDG_CACHE_HOME/sonicop`, or
192
- `~/Library/Caches/sonicop` on macOS, or `~/.cache/sonicop`. `--cache-root` cannot be combined
193
- with `--cache false`.
154
+ - Caching is on by default. `--cache false` turns it off, as does `AllCops/MaxFilesInCache: 0` in a configuration file.
155
+ - `--cache-root DIR` chooses where it lives. Without it the root is `$XDG_CACHE_HOME/sonicop`, or `~/Library/Caches/sonicop` on macOS, or `~/.cache/sonicop`. `--cache-root` cannot be combined with `--cache false`.
194
156
  - `AllCops/MaxFilesInCache` bounds how many reports are kept, defaulting to RuboCop's 20,000.
195
157
  - Autocorrect runs, `--stdin`, `--profile` and `--memory` neither read nor write it.
196
- - It is not shared with RuboCop's cache: the formats are unrelated, and an entry is only served back
197
- to a build of Sonicop identical to the one that wrote it.
198
-
199
- Cop settings are the silent case. A setting sonicop does not implement is ignored without
200
- any warning, and so is a setting whose name is simply misspelled. **A run that reports no offenses
201
- is therefore not evidence that a setting took effect**, because an ignored setting and a clean file
202
- produce the same output. *Cop conformance* above says which values have been measured.
203
-
204
- Cop *names* are checked: an unrecognised cop in a configuration file stops the run with an error.
205
- It is the settings inside a recognised cop that pass unvalidated.
206
-
207
- ### Conformance
208
-
209
- The implemented cops are verified against RuboCop 1.89.0 over five Ruby projects — RuboCop itself,
210
- Rails, Ruby, Homebrew and Mastodon — totalling 18,251 files, with the upstream default
211
- configuration on both sides. Every offense is compared by cop, path, line, column, last line, last
212
- column, length, message, severity and correctability.
213
-
214
- Three of the five match **exactly**: RuboCop's own tree (5,766 offenses), Rails (167,760) and
215
- Mastodon (15,286), with no excess, no shortfall and no metadata differences. The target file lists
216
- match exactly on all five — paths, not just counts, compared as sets. What remains is concentrated in
217
- `Lint/Syntax`. Most of it is RuboCop's
218
- LALR parser recovering from an error and emitting diagnostics a tree-sitter parse cannot
219
- reconstruct, and the resulting position differences go in both directions. On Homebrew all 997
220
- missing and 263 excess positions are `Lint/Syntax`, but the **sets of files rejected as syntax
221
- errors are exactly the same: 569 versus 569**, with no file rejected only by Sonicop. The 263 excess
222
- positions occur in 135 shared syntax-error files and every one follows a diagnostic at a position
223
- shared by both tools, so they are recovery-position differences rather than a separate acceptance
224
- bug. At Ruby 3.1, which supports the syntax used there, both tools report zero `Lint/Syntax` offenses.
225
- Autocorrect is byte-identical on RuboCop's own tree and on Mastodon, the two corpora held as a hard
226
- line: a change that breaks byte equality there is a regression, not a new known divergence.
227
-
228
- See [CONFORMANCE.md](CONFORMANCE.md) for the commands, the corpus commits these counts were
229
- measured at, and the two ways a measurement of this kind can mislead you.
230
-
231
- ### Performance
232
-
233
- Measured over all five conformance corpora. Both tools were given their own bundled default
234
- configuration (`--force-default-config`), so neither reads the project's `.rubocop.yml`, and on every
235
- corpus the two resolve **the same number of files** — which is what these timings need, since it
236
- means neither side is inspecting less. Path-by-path equality is a stronger claim, established under
237
- *Conformance* above for all five, but on the pinned corpus revisions rather than on these timing runs.
238
-
239
- Both tools run their full default set — **the same 394 cops**, matched name for name — so neither
240
- side is restricted and the comparison is like-for-like as it stands. (394 is what is left of the 609
241
- once the 159 RuboCop ships as `Enabled: pending` and the 56 it ships as `Enabled: false` are set
242
- aside; a default run reaches neither group on either side.) Times are the fastest of two
243
- warmed runs.
158
+ - It is not shared with RuboCop's cache: the formats are unrelated, and an entry is only served back to a build of Sonicop identical to the one that wrote it.
159
+
160
+ Cop settings are the silent case. A setting sonicop does not implement is ignored without any warning, and so is a setting whose name is simply misspelled. **A run that reports no offenses is therefore not evidence that a setting took effect**, because an ignored setting and a clean file produce the same output. [Non-default settings](docs/cop-conformance.md#non-default-settings) says which values have been measured.
161
+
162
+ Cop *names* are checked: an unrecognised cop in a configuration file stops the run with an error. It is the settings inside a recognised cop that pass unvalidated.
163
+
164
+ ## Conformance
165
+
166
+ The implemented cops are verified against RuboCop 1.89.0 over five Ruby projects — RuboCop itself, Rails, Ruby, Homebrew and Mastodon — totalling 18,251 files, with the upstream default configuration on both sides. Every offense is compared by cop, path, line, column, last line, last column, length, message, severity and correctability.
167
+
168
+ Three of the five match **exactly**: RuboCop's own tree (5,766 offenses), Rails (167,760) and Mastodon (15,286), with no excess, no shortfall and no metadata differences. The target file lists match exactly on all five — paths, not just counts, compared as sets. What remains is concentrated in `Lint/Syntax`. Most of it is RuboCop's LALR parser recovering from an error and emitting diagnostics a tree-sitter parse cannot reconstruct, and the resulting position differences go in both directions. On Homebrew all 997 missing and 263 excess positions are `Lint/Syntax`, but the **sets of files rejected as syntax errors are exactly the same: 569 versus 569**, with no file rejected only by Sonicop. The 263 excess positions occur in 135 shared syntax-error files and every one follows a diagnostic at a position shared by both tools, so they are recovery-position differences rather than a separate acceptance bug. At Ruby 3.1, which supports the syntax used there, both tools report zero `Lint/Syntax` offenses. Autocorrect is byte-identical on RuboCop's own tree and on Mastodon, the two corpora held as a hard line: a change that breaks byte equality there is a regression, not a new known divergence.
169
+
170
+ See [CONFORMANCE.md](CONFORMANCE.md) for the commands, the corpus commits these counts were measured at, and the two ways a measurement of this kind can mislead you.
171
+
172
+ ## Performance
173
+
174
+ Measured over all five conformance corpora. Both tools were given their own bundled default configuration (`--force-default-config`), so neither reads the project's `.rubocop.yml`, and on every corpus the two resolve **the same number of files** — which is what these timings need, since it means neither side is inspecting less. Path-by-path equality is a stronger claim, established under [Conformance](#conformance) above for all five, but on the pinned corpus revisions rather than on these timing runs.
175
+
176
+ Both tools run their full default set — **the same 394 cops**, matched name for name — so neither side is restricted and the comparison is like-for-like as it stands. (394 is what is left of the 609 once the 159 RuboCop ships as `Enabled: pending` and the 56 it ships as `Enabled: false` are set aside; a default run reaches neither group on either side.) Times are the fastest of two warmed runs.
244
177
 
245
178
  | Corpus | Files | Offenses | RuboCop parallel | Sonicop parallel | RuboCop single | Sonicop single |
246
179
  |---|---:|---:|---:|---:|---:|---:|
@@ -250,29 +183,11 @@ warmed runs.
250
183
  | rails/rails | 3,562 | 168,615 | 32.90 s | **8.84 s** | 85.71 s | **19.17 s** |
251
184
  | ruby/ruby | 7,477 | 765,975 | 86.89 s | **15.59 s** | 193.59 s | **37.98 s** |
252
185
 
253
- The gap is 3.7x to 7.4x in parallel and 4.5x to 6.2x single-process, so no single corpus summarizes
254
- it. **Read the single-process column and treat the parallel one as indicative.** Measuring the same
255
- two binaries three times over a day put the single-process figures within 16% of each other every
256
- time, while the parallel ratio on RuboCop's own tree moved between 3.3x and 9.2x purely with what
257
- else the machine was doing. Single-process measures the engines; parallel measures the engines plus
258
- how well each one's scheduling happens to fit that tree on that run.
259
-
260
- The speed is not bought by skipping work. Over those same 394 cops the two find the **same number of
261
- offenses** on every corpus in the table, and on RuboCop's own tree and on Mastodon every one of them
262
- is at the same position with the same message and severity. Rails, at 168,615 offenses, differs in
263
- two of them — one `Style/CaseLikeIf` Sonicop reports and one `Metrics/AbcSize` it does not — and
264
- RuboCop's own tree differs in one offense's `correctable` flag. Autocorrect is byte-identical on the
265
- first and the last.
266
-
267
- Four details matter for reproducing this. RuboCop **silently turns `--parallel` off when combined
268
- with `--cache false`**, so its parallel runs here use a cache directory that is deleted before each
269
- run rather than disabled; timing it with `--cache false --parallel` measures a single process and
270
- overstates the difference. RuboCop's default is a single process, while Sonicop is parallel unless
271
- `--no-parallel` is passed. Both sides need a **cold** cache: Sonicop caches by default too, so a
272
- second run over the same tree answers from its own cache and measures nothing about the engine —
273
- give each tool a throwaway cache root. And the cache root must be a **real path**: macOS `mktemp -d`
274
- returns `/var/folders/…`, whose `/var` is a symlink, and RuboCop refuses such a location and runs
275
- with no cache at all.
186
+ The gap is 3.7x to 7.4x in parallel and 4.5x to 6.2x single-process, so no single corpus summarizes it. **Read the single-process column and treat the parallel one as indicative.** Measuring the same two binaries three times over a day put the single-process figures within 16% of each other every time, while the parallel ratio on RuboCop's own tree moved between 3.3x and 9.2x purely with what else the machine was doing. Single-process measures the engines; parallel measures the engines plus how well each one's scheduling happens to fit that tree on that run.
187
+
188
+ The speed is not bought by skipping work. Over those same 394 cops the two find the **same number of offenses** on every corpus in the table, and on RuboCop's own tree and on Mastodon every one of them is at the same position with the same message and severity. Rails, at 168,615 offenses, differs in two of them — one `Style/CaseLikeIf` Sonicop reports and one `Metrics/AbcSize` it does not — and RuboCop's own tree differs in one offense's `correctable` flag. Autocorrect is byte-identical on the first and the last.
189
+
190
+ Four details matter for reproducing this. RuboCop **silently turns `--parallel` off when combined with `--cache false`**, so its parallel runs here use a cache directory that is deleted before each run rather than disabled; timing it with `--cache false --parallel` measures a single process and overstates the difference. RuboCop's default is a single process, while Sonicop is parallel unless `--no-parallel` is passed. Both sides need a **cold** cache: Sonicop caches by default too, so a second run over the same tree answers from its own cache and measures nothing about the engine — give each tool a throwaway cache root. And the cache root must be a **real path**: macOS `mktemp -d` returns `/var/folders/…`, whose `/var` is a symlink, and RuboCop refuses such a location and runs with no cache at all.
276
191
 
277
192
  ```bash
278
193
  # RuboCop, parallel, cold cache, its full default set of 394 cops
@@ -284,72 +199,45 @@ rubocop --force-default-config --cache true --cache-root "$root" \
284
199
  sonicop --force-default-config --cache-root "$root" --format quiet
285
200
  ```
286
201
 
287
- Writing the cache is part of these numbers, and it is not free: the index holds every offense with
288
- the source line it was found on, which is 336 MB over `ruby/ruby`. A second run against a warm cache
289
- answers in 1.59 s there, and in 0.42 s over Rails.
290
-
291
- Machine: Apple M2 (8 cores), Ruby 4.0.6 with YJIT available, RubyGems-installed RuboCop 1.89.0.
292
- Measured on 2026-08-31 against the corpora at `rubocop_rubocop` 2693129, `mastodon_mastodon` b59ddc7,
293
- `Homebrew_brew` b42173b, `rails_rails` a19f07f and `ruby_ruby` 22e4a75. The one-minute load average
294
- was between 4.5 and 7.1 as each row was taken — most of it RuboCop's own parallel workers, which is
295
- inherent to measuring them. **The machine was in use, not idle.** Both tools ran back to back under
296
- the same conditions on each corpus, so the ratios hold, but the absolute seconds are not a floor:
297
- expect better on a quiet machine. Anything competing for cores inflates both sides, and not by the
298
- same factor on each, which is what makes the parallel column move as much as it does. If the
299
- absolute numbers matter to you, measure on an idle machine and record the load either side of the
300
- run — a figure without that context cannot be compared with another one.
202
+ Writing the cache is part of these numbers, and it is not free: the index holds every offense with the source line it was found on, which is 336 MB over `ruby/ruby`. A second run against a warm cache answers in 1.59 s there, and in 0.42 s over Rails.
203
+
204
+ Machine: Apple M2 (8 cores), Ruby 4.0.6 with YJIT available, RubyGems-installed RuboCop 1.89.0. Measured on 2026-08-31 against the corpora at `rubocop_rubocop` 2693129, `mastodon_mastodon` b59ddc7, `Homebrew_brew` b42173b, `rails_rails` a19f07f and `ruby_ruby` 22e4a75. The one-minute load average was between 4.5 and 7.1 as each row was taken — most of it RuboCop's own parallel workers, which is inherent to measuring them. **The machine was in use, not idle.** Both tools ran back to back under the same conditions on each corpus, so the ratios hold, but the absolute seconds are not a floor: expect better on a quiet machine. Anything competing for cores inflates both sides, and not by the same factor on each, which is what makes the parallel column move as much as it does. If the absolute numbers matter to you, measure on an idle machine and record the load either side of the run — a figure without that context cannot be compared with another one.
301
205
 
302
206
  ## Development
303
207
 
304
- `make` is the single entry point; `make help` lists every target. The Rakefile holds the gem
305
- packaging tasks that `make` delegates to.
208
+ <!-- standard:dev:start -->
209
+ Requires [mise](https://mise.jdx.dev/). Tool versions are pinned in `mise.toml`.
306
210
 
307
211
  ```bash
308
- make build # debug build
309
- make check # fmt, clippy, Rust tests, Ruby wrapper tests, version consistency
310
- make gem # source gem
212
+ make setup # Install the toolchain (mise) and dependencies
213
+ make ci # Run the same checks as CI (no changes)
311
214
  ```
312
215
 
313
- ### Adding a cop
314
-
315
- A cop is one file under `src/rules/<department>/<cop>.rs` exposing a single
316
- `check(context, offenses)`, plus one line in that department's `mod.rs`:
317
-
318
- ```rust
319
- department_rules! {
320
- "Layout";
321
- line_length => ("LineLength", Convention),
322
- }
323
- ```
324
-
325
- That line is the only place the cop's name and default severity are written. Inside the cop the
326
- name stays implicit: `context.setting("Max")` reads `Layout/LineLength: Max`, and
327
- `context.offense(message, range)` reports under the cop's own name at its configured severity. A
328
- cop that spelled its name a second time could disagree with the registry, and nothing in the type
329
- system would catch it.
330
-
331
- Prefer `context.nodes_of("kind")` over walking every node: each cop runs on every file, so a full
332
- walk per cop is what makes inspection scale with the registry rather than with the file.
333
-
334
- `Cargo.toml` is the single source of truth for the version. `lib/sonicop/version.rb` is generated
335
- from it by `make version-sync` and committed, because the gemspec reads it at package time. CI
336
- fails when the two disagree.
337
-
338
- `config/default.yml` is vendored from upstream RuboCop; re-fetch it with
339
- `scripts/sync_default_yml.sh <rubocop-version>`, which records the source version in the file
340
- header.
341
-
342
- `src/display_width_table.rs` is generated and committed too. RuboCop measures display columns with
343
- the `unicode-display_width` gem, so the table is taken from the gem rather than restated by hand —
344
- an exception table written out by hand had already drifted far enough to draw the wrong number of
345
- carets under decomposed Japanese. Regenerate it with
346
- `ruby scripts/dump_display_width.rb > src/display_width_table.rs`, which records the gem and Unicode
347
- versions in the file header.
348
-
349
- Dependencies are updated with `depup --install`. The Ruby grammar dependency is pinned to an exact
350
- fork commit in `Cargo.toml` for reproducible builds.
216
+ | Command | Description |
217
+ |---|---|
218
+ | `make setup` | Install the toolchain (mise) and dependencies |
219
+ | `make build` | Build a debug binary |
220
+ | `make release` | Build a release binary |
221
+ | `make run` | Run the debug binary (arguments via ARGS="...") |
222
+ | `make test` | Run the tests |
223
+ | `make lint` | Run clippy with warnings as errors |
224
+ | `make fmt` | Format the code (rewrites files) |
225
+ | `make fmt-check` | Check the formatting (no changes) |
226
+ | `make check` | Run fmt-check and lint (no changes) |
227
+ | `make ci` | Run the same checks as CI (no changes) |
228
+ | `make install` | Install the release binary to INSTALL_PATH (default /usr/local/bin) |
229
+ | `make uninstall` | Remove the binary from INSTALL_PATH |
230
+ | `make clean` | Remove build artifacts |
231
+
232
+ Run `make` to list every target. Releases are published from GitHub Actions (**Actions → Release → Run workflow**).
233
+ <!-- standard:dev:end -->
234
+
235
+ The Rakefile holds only the gem packaging and version tasks, which `make gem`, `make gem-platform` and `make version-sync` call, and `Cargo.toml` is the single source of truth for the version. Adding a cop, the files generated from upstream, and the other targets: [docs/development.md](docs/development.md).
351
236
 
352
237
  ## License
353
238
 
354
- [MIT](LICENSE). The bundled RuboCop default configuration and parser dependency retain their
355
- upstream notices in [NOTICE](NOTICE) and [`licenses/`](licenses/).
239
+ <!-- standard:license:start -->
240
+ [MIT](LICENSE)
241
+ <!-- standard:license:end -->
242
+
243
+ The bundled RuboCop default configuration and parser dependency retain their upstream notices in [NOTICE](NOTICE) and [`licenses/`](licenses/).
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Generated from Cargo.toml by `rake version:sync`. Do not edit by hand.
4
4
  module Sonicop
5
- VERSION = '26.9.100'
5
+ VERSION = '26.9.101'
6
6
  end
data/libexec/sonicop CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sonicop
3
3
  version: !ruby/object:Gem::Version
4
- version: 26.9.100
4
+ version: 26.9.101
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Yohei