sonicop 26.9.101-aarch64-linux → 26.9.102-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 +4 -4
- data/CONFORMANCE.md +45 -333
- data/README.ja.md +24 -30
- data/README.md +24 -30
- data/lib/sonicop/version.rb +1 -1
- data/libexec/sonicop +0 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2214e68a4f33b973256ffe2ad8705905df7924c90e6c0b23acf74b1b9f213fe1
|
|
4
|
+
data.tar.gz: 8917055df4d29b80bef6af3f1b37666cf28791c808e5e75652e5ee225c1d0b3d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: af4fb0ddb2ab34c2fb71010811444a32a63cd7ec717aff5ce3000c61a2901439d99df468502fd0cc1e36a663f80d09007f7091272373b84b5b8a26b456611d53
|
|
7
|
+
data.tar.gz: ecb1645756570b2dbad6da532f2af6ecd12b4704ec10a132dd6f55a7d4ab1aeed7d77b86651bcfd0d82e1af6866adbcbc0e772b8d0306f16cf9028d3f0eba8f1
|
data/CONFORMANCE.md
CHANGED
|
@@ -1,357 +1,69 @@
|
|
|
1
1
|
# RuboCop conformance
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Reference: RuboCop 1.89.0 on Ruby 4.0.6
|
|
5
|
-
Configuration: RuboCop 1.89.0 built-in defaults (`--force-default-config`) on both sides
|
|
3
|
+
Measured: 2026-09-27. The behavioral reference is **RuboCop 1.89.0 on Ruby 4.0.6**; Sonicop's vendored `config/default.yml` comes from that release. Both tools used `--force-default-config`. The current upstream gem may have more cops; a newer release is used only for the separate speed comparison in the README.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
Re-fetch it with `scripts/sync_default_yml.sh <rubocop-version>`.
|
|
5
|
+
## Scope and method
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
Sonicop implements **609 / 609** RuboCop 1.89.0 cops, with the same names and no extras: 394 enabled, 159 pending, and 56 disabled by default. These are registry counts. A default run exercises only the enabled group. The separate [cop conformance table](README.md#cop-conformance) records which cops actually fired on upstream spec cases; a cop that stayed silent is never counted as an exact match.
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
by offense. An offense is keyed by cop name, path, line and column; at each shared key the last
|
|
14
|
-
line, last column, length, message, severity and correctability are compared as well.
|
|
9
|
+
The five public corpora below have **18,251** target files in total. RuboCop and Sonicop resolve identical target *path sets* on all five. Each corpus is pinned to a commit; numbers from another revision are not interchangeable. For each common offense position `(cop, path, line, column)`, we also compare end line, end column, length, message, severity and correctability. Equal total offense counts alone do not establish a match.
|
|
15
10
|
|
|
16
|
-
| Corpus |
|
|
17
|
-
|
|
18
|
-
| rubocop/rubocop | `f009b33` | 1,765 | 5,766 |
|
|
19
|
-
| rails/rails | `62b5458` | 3,551 | 167,760 |
|
|
20
|
-
| ruby/ruby | `3349f41` | 7,466 | 761,578 |
|
|
21
|
-
| Homebrew/brew | `38ee325` | 2,179 | 49,920 |
|
|
22
|
-
| mastodon/mastodon | `fad3685` | 3,290 | 15,286 |
|
|
11
|
+
| Corpus | Revision | Target files | RuboCop offenses | Sonicop offenses |
|
|
12
|
+
|---|---|---:|---:|---:|
|
|
13
|
+
| rubocop/rubocop | `f009b33` | 1,765 | 5,766 | 5,766 |
|
|
14
|
+
| rails/rails | `62b5458` | 3,551 | 167,760 | 167,760 |
|
|
15
|
+
| ruby/ruby | `3349f41` | 7,466 | 761,578 | 761,188 |
|
|
16
|
+
| Homebrew/brew | `38ee325` | 2,179 | 49,920 | 49,326 |
|
|
17
|
+
| mastodon/mastodon | `fad3685` | 3,290 | 15,286 | 15,286 |
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
comparing, or a difference in the corpus will read as a difference in the port.
|
|
19
|
+
Ruby's reference was assembled in chunks. RuboCop raises an invalid UTF-8 error on `test/ruby/test_regexp.rb`, and its JSON formatter cannot serialize an offense from `test/ruby/test_file_exhaustive.rb`. Those **two files are excluded from the Ruby offense comparison**, which covers 7,464 files; both tools still discover the same 7,466 target paths. A single RuboCop invocation can emit well-formed but incomplete JSON after a crash. Every comparison checks that its inspected count reached its target count or records the excluded files explicitly.
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
`test/ruby/test_regexp.rb` cannot be inspected at all — RuboCop's lexer raises `invalid byte
|
|
29
|
-
sequence in UTF-8` on it — and `test/ruby/test_file_exhaustive.rb` holds an offense whose text is
|
|
30
|
-
not valid UTF-8, which stops the JSON formatter from emitting a document. The comparison for that
|
|
31
|
-
corpus therefore covers 7,464 of its 7,466 files.
|
|
21
|
+
## Detection results
|
|
32
22
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
directory symlinks, and treats a hidden path by its *first* component rather than by any dot in it.
|
|
23
|
+
| Corpus | Sonicop-only positions | RuboCop-only positions | Shared-position field differences | Interpretation |
|
|
24
|
+
|---|---:|---:|---:|---|
|
|
25
|
+
| rubocop/rubocop | 0 | 0 | 0 | Exact |
|
|
26
|
+
| rails/rails | 0 | 0 | 0 | Exact |
|
|
27
|
+
| ruby/ruby | 168 | 558 | 4 | Parser recovery, grammar and remaining cop differences |
|
|
28
|
+
| Homebrew/brew | 330 | 924 | 0 | All positions differ in `Lint/Syntax` |
|
|
29
|
+
| mastodon/mastodon | 0 | 0 | 0 | Exact |
|
|
41
30
|
|
|
42
|
-
|
|
43
|
-
side. That includes the 159 RuboCop ships as `Enabled: pending` and the 56 it ships as
|
|
44
|
-
`Enabled: false`, neither of which a default run reaches — checking those means naming them with
|
|
45
|
-
`--only` or switching them on in a configuration, exactly as with RuboCop.
|
|
31
|
+
Homebrew has the same **569 syntax-error files** on both sides. The different `Lint/Syntax` positions are produced after error recovery; they do not represent different accepted-file sets there. Ruby has both parser and cop residue. Its four shared-position differences are two `correctable` flags, one range length, and one syntax message. These differences remain open and are not described as exact matches.
|
|
46
32
|
|
|
47
|
-
|
|
48
|
-
already like for like. RuboCop's extension plugins are not installed, so this measures the range
|
|
49
|
-
that can be checked without them.
|
|
33
|
+
A separate CRLF probe used the pinned `rubocop/rubocop` revision `f009b33`, converted 1,748 Ruby files in a copy, and inspected the same 1,765 target paths on both sides. The **7,514 offense records** matched in full, including range, message, severity and correctability; neither side had an extra record.
|
|
50
34
|
|
|
51
|
-
|
|
52
|
-
rubocop --force-default-config --cache false -f json
|
|
53
|
-
sonicop --force-default-config --format json
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Results
|
|
57
|
-
|
|
58
|
-
Three kinds of difference are counted separately because they mean different things. The set of
|
|
59
|
-
files with a fatal `Lint/Syntax` offense says whether the parsers disagree about accepting a file.
|
|
60
|
-
`Lint/Syntax` positions inside a file both already rejected measure diagnostic recovery, not file
|
|
61
|
-
acceptance. A difference in any other cop says the port reads the same tree and draws a different
|
|
62
|
-
conclusion. Only the last category is a defect in a cop.
|
|
63
|
-
|
|
64
|
-
| Corpus | Excess | Missing | of which `Lint/Syntax` | Other cops | Field differences | Measured |
|
|
65
|
-
|---|---:|---:|---|---:|---|---|
|
|
66
|
-
| rubocop/rubocop | 0 | 0 | — | 0 | correctable ×1 | 2026-08-17 |
|
|
67
|
-
| rails/rails | 0 | 0 | — | 0 | none | 2026-08-17 |
|
|
68
|
-
| mastodon/mastodon | 0 | 0 | — | 0 | none | 2026-08-17 |
|
|
69
|
-
| Homebrew/brew | 263 | 997 | **all of them** | **0** | none | 2026-08-18 |
|
|
70
|
-
| ruby/ruby | 142 | 585 | 117 missing, 44 excess | 92 | 5 | 2026-08-16 |
|
|
71
|
-
|
|
72
|
-
The last column is per row on purpose. A single date at the top of the file would say the five were
|
|
73
|
-
measured together, and they were not: the first four are re-measured on every release, ruby/ruby is
|
|
74
|
-
not — it is the one corpus whose reference cannot be produced in a single run (see above), so it is
|
|
75
|
-
re-measured deliberately rather than routinely. Its row is the older of the two and should be read
|
|
76
|
-
as such.
|
|
77
|
-
|
|
78
|
-
**No cop other than `Lint/Syntax` differs on four of the five corpora**, Homebrew included: across
|
|
79
|
-
its 2,179 files and 49,920 offenses the two agree on every position, message, severity and
|
|
80
|
-
correctability that is not a syntax diagnostic.
|
|
81
|
-
|
|
82
|
-
Homebrew's 1,260 differences — 997 missing and 263 excess — share one cause, and it is not a parser
|
|
83
|
-
bug on either side. Homebrew is a Ruby 4.0
|
|
84
|
-
codebase — `Library/Homebrew/.ruby-version` says `4.0.6` — but that file sits below the directory the
|
|
85
|
-
run starts from, and `TargetRubyVersion` is only inferred from the working directory's ancestors. Both
|
|
86
|
-
tools therefore fall back to the default of 2.7 (RuboCop says so itself: `Using Ruby 2.7 parser`) and
|
|
87
|
-
both call `dry_run:,` — a hash value omission, valid since 3.1 — a syntax error. **Run the same corpus
|
|
88
|
-
at 3.1 and both report zero `Lint/Syntax`.** What is left is not disagreement about which files parse:
|
|
89
|
-
the file sets are identical, 569 on each side, with no file unique to either parser. The two parsers
|
|
90
|
-
recover differently after encountering invalid syntax, which puts diagnostic positions on both sides
|
|
91
|
-
of the ledger rather than only on RuboCop's.
|
|
92
|
-
|
|
93
|
-
All 263 Sonicop-only positions were classified mechanically, not inferred from their cop name. They
|
|
94
|
-
occur in 135 of those shared syntax-error files, and every one follows a `Lint/Syntax` position that
|
|
95
|
-
both tools reported in the same file. Their messages are 113 end-of-input (`$end`), 86 right
|
|
96
|
-
parentheses (`tRPAREN`), 53 commas (`tCOMMA`), seven `end` keywords (`kEND`), two equals signs
|
|
97
|
-
(`tEQL`), one `when` (`kWHEN`) and one identifier (`tIDENTIFIER`). Thus the former “excess” is not a
|
|
98
|
-
set of valid files rejected only by Sonicop; it is the other direction of the same diagnostic-recovery
|
|
99
|
-
divergence as the 997 RuboCop-only positions. Suppressing every diagnostic after Sonicop's first one
|
|
100
|
-
would make the excess zero, but would also discard 1,998 positions that already match and increase the
|
|
101
|
-
missing side from 997 to 2,995. That is not a conformance fix. See *Known divergences*.
|
|
102
|
-
|
|
103
|
-
Much of ruby/ruby's difference is the same shape,
|
|
104
|
-
with 117 of the 585 missing and 44 of the 142 excess being `Lint/Syntax` itself,
|
|
105
|
-
and the rest follows from it: a file the two disagree about is inspected by one tool and skipped by
|
|
106
|
-
the other, so every offense in it lands on one side of the ledger. Counting that through, 635 of the
|
|
107
|
-
727 differences sit in files one tool or the other calls a syntax error, and they are spread thinly
|
|
108
|
-
across the Layout department rather than sitting in one cop. Of the 92 that do not, 77 are the
|
|
109
|
-
`Style/RedundantParentheses` case under *Differences that should not be closed*, which leaves 15 —
|
|
110
|
-
ten of them in TRICK entries or encoding fixtures. Both are explained under *Known divergences*.
|
|
111
|
-
|
|
112
|
-
ruby/ruby also carries five differences at positions both tools reported: two `correctable` flags and
|
|
113
|
-
one `last_column`/`length` pair, all on indentation cops inside files the two parse differently, plus
|
|
114
|
-
one `Lint/Syntax` message naming a different token (`tLCURLY` where RuboCop says `tLAMBEG`).
|
|
115
|
-
|
|
116
|
-
Autocorrect is compared the same way, byte for byte over the whole tree: run `-A` with both tools
|
|
117
|
-
from a clean checkout and diff the results.
|
|
118
|
-
|
|
119
|
-
| Corpus | Corrected tree |
|
|
120
|
-
|---|---|
|
|
121
|
-
| rubocop/rubocop | **identical** |
|
|
122
|
-
| mastodon/mastodon | **identical** |
|
|
123
|
-
| Homebrew/brew | 2 files differ |
|
|
124
|
-
| rails/rails | 25 files differ |
|
|
125
|
-
| ruby/ruby | not measurable |
|
|
126
|
-
|
|
127
|
-
RuboCop's own tree and Mastodon are the hard line: a change that breaks byte equality on either is a
|
|
128
|
-
regression to be fixed, not a new known divergence to be recorded.
|
|
129
|
-
|
|
130
|
-
The rails/rails residue is one shape, and Homebrew contributes one more of it: the body of a
|
|
131
|
-
`begin`/`rescue`/`end` ends up indented two columns off. It appears only in a full `-A` run — the
|
|
132
|
-
first-pass detection matches exactly, and reducing the case to a single file reproduces nothing —
|
|
133
|
-
because what differs is which correction pass a nested indentation fix lands in. Homebrew's other
|
|
134
|
-
file pairs a `disable`/`enable` around `Lint/EmptyBlock`, a cop RuboCop ships as pending; the
|
|
135
|
-
upstream run leaves both comments alone and Sonicop removes them.
|
|
136
|
-
|
|
137
|
-
ruby/ruby cannot be measured this way at all: RuboCop's own run does not finish on it (see *Reading a
|
|
138
|
-
measurement*), so there is no complete reference tree to diff against.
|
|
139
|
-
|
|
140
|
-
Run the comparison on a copy of the corpus. Autocorrect rewrites the tree in place, so a run that
|
|
141
|
-
shares a checkout with anything else — another comparison, a lint measurement — has both tools
|
|
142
|
-
reading different files and reports differences that are not there.
|
|
143
|
-
|
|
144
|
-
## Reading a measurement
|
|
145
|
-
|
|
146
|
-
Two traps make a run look better or worse than it is, and both have produced wrong conclusions here.
|
|
147
|
-
|
|
148
|
-
**Counts cancel out.** Comparing offense totals hides an excess and a shortfall of the same size.
|
|
149
|
-
Compare the *set* of locations, then compare fields at the locations both tools produced.
|
|
150
|
-
|
|
151
|
-
**RuboCop can stop early.** On ruby/ruby its lexer raises `invalid byte sequence in UTF-8` on
|
|
152
|
-
`test/ruby/test_regexp.rb` and the whole run unwinds. A single-invocation run reports 6,909 of 7,466
|
|
153
|
-
files inspected, and the 557 files after that one in sort order were never looked at — so every
|
|
154
|
-
offense Sonicop found in them counts as "excess". Check `summary.inspected_file_count` against
|
|
155
|
-
`summary.target_file_count` on every run. To get a complete reference for that corpus, split the file
|
|
156
|
-
list into chunks and re-run each chunk past the file that killed it.
|
|
157
|
-
|
|
158
|
-
Read those two counts with `-f offenses`, not `-f json`: with the full cop set the JSON formatter
|
|
159
|
-
never gets to write them (see below), and the run looks like a crash rather than a truncation.
|
|
160
|
-
|
|
161
|
-
**And sometimes it emits nothing at all.** `test/ruby/test_file_exhaustive.rb` holds an offense whose
|
|
162
|
-
text is not valid UTF-8, and `JSONFormatter#finished` raises `source sequence is illegal/malformed
|
|
163
|
-
utf-8` while serializing — before a single byte reaches stdout. The failure is not per-file but
|
|
164
|
-
per-document: one such file discards the JSON for the entire run, summary included, which is why the
|
|
165
|
-
counts above have to come from another formatter.
|
|
166
|
-
|
|
167
|
-
Two things about this are easy to get backwards. **What matters is the encoding of the offense text,
|
|
168
|
-
not of the source.** `test/ruby/enc/test_euc_jp.rb` and `test/ruby/enc/test_shift_jis.rb` do hold
|
|
169
|
-
bytes that are not valid UTF-8, yet both serialize fine on their own — their magic comments make
|
|
170
|
-
RuboCop read them in their declared encoding — while `test_file_exhaustive.rb` is valid UTF-8 as a
|
|
171
|
-
file. **And which files are affected depends on the cop set, not on the corpus.** Running the full
|
|
172
|
-
609 excludes two files from ruby/ruby; running `--only Lint/LiteralAsCondition,Layout/IndentationWidth`
|
|
173
|
-
excludes one, because neither of those two cops produces an offense whose text is ill-formed. A
|
|
174
|
-
recorded exclusion count is part of a measurement's conditions, not a property of the corpus.
|
|
175
|
-
|
|
176
|
-
A chunked reference run has to tell this apart from the case above: retrying one file at a time is
|
|
177
|
-
right when a file killed the parser, and useless when the run never had a chance to start. Two other
|
|
178
|
-
stdout notices break JSON the same way — `--parallel` being ignored under `--cache false`, and the
|
|
179
|
-
plugin suggestions RuboCop prints after a run — so a reference reader should skip to the first `{`
|
|
180
|
-
rather than trust byte zero.
|
|
181
|
-
|
|
182
|
-
## Known divergences
|
|
183
|
-
|
|
184
|
-
`tests/conformance/known_divergences.yml` is the machine-checked record: a divergence that is listed
|
|
185
|
-
but no longer reproduces fails the test, and so does one that appears without being listed.
|
|
186
|
-
|
|
187
|
-
### Error recovery after a syntax error
|
|
35
|
+
The default configuration is the required baseline. Native project configurations were also attempted. Their plugin requirements stop RuboCop before inspection on four corpora, so no native-config agreement is claimed for them. For the Ruby corpus, a nested native-config run over `spec/ruby` inspected 4,435 files and matched offense by offense after a directory-discovery fix. Directory target discovery uses the configuration of the starting directory; the cop configuration is resolved for each inspected file.
|
|
188
36
|
|
|
189
|
-
|
|
190
|
-
further diagnostics from the recovered state. Sonicop parses with tree-sitter and reaches different
|
|
191
|
-
recovery states. On Homebrew this produces 997 RuboCop-only and 263 Sonicop-only `Lint/Syntax`
|
|
192
|
-
positions: examples on the RuboCop side include `class definition in method body`, `dynamic constant
|
|
193
|
-
assignment`, `cannot assign to a keyword`, and repeated `unexpected token` inside one multi-line hash.
|
|
37
|
+
## Autocorrect and safety
|
|
194
38
|
|
|
195
|
-
|
|
196
|
-
decides whether a file is inspected at all: RuboCop runs no cop other than `Lint/Syntax` on a file
|
|
197
|
-
that does not parse, and Sonicop does the same. On Homebrew the two agree on the *set*, not merely on
|
|
198
|
-
its size: 569 files, with the difference in either direction empty. This is why every cop other than
|
|
199
|
-
`Lint/Syntax` matches exactly there despite the 1,260 offenses of difference.
|
|
39
|
+
`-a` and `-A` rewrite files, so each tool runs on its own copy of the same clean tree. Corrected trees are compared byte for byte, including non-`.rb` files. The known differences are pinned by file and output hash in `tests/conformance/known_divergences.yml`; a changed hash or an unregistered difference fails the gate.
|
|
200
40
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
41
|
+
| Corpus | `-a` corrected-tree differences | `-A` corrected-tree differences |
|
|
42
|
+
|---|---:|---:|
|
|
43
|
+
| rubocop/rubocop | 0 | 1 known safety difference |
|
|
44
|
+
| rails/rails | 4 known safety differences | 3 known safety differences |
|
|
45
|
+
| mastodon/mastodon | 3 known safety differences | 3 known safety differences |
|
|
46
|
+
| Homebrew/brew | 0 | 0 |
|
|
47
|
+
| ruby/ruby | Reference aborts | Reference aborts |
|
|
205
48
|
|
|
206
|
-
|
|
207
|
-
same first diagnostic (position and message), and 222 report an identical list end to end. In all 569,
|
|
208
|
-
Sonicop's earliest source-position diagnostic is also present in RuboCop's output. The 277 files in
|
|
209
|
-
between are the divergence in its purest form — the two agree on at least one error position and part
|
|
210
|
-
company as recovery proceeds. The 70
|
|
211
|
-
files whose first diagnostic differs follow one shape — an endless method definition (`def to_s =
|
|
212
|
-
to_str`, valid from Ruby 3.0, rejected by the default `TargetRubyVersion: 2.7`) leaves `parser`'s
|
|
213
|
-
method context open, so the enclosing `class` emits `class definition in method body` when it is
|
|
214
|
-
finally reduced. The diagnostic is reported at the `class` keyword, far above the line that actually
|
|
215
|
-
failed, which puts it first in source order.
|
|
49
|
+
RuboCop's own autocorrect can produce invalid Ruby in cases that Sonicop's syntax guard leaves untouched. Other recorded exceptions prevent an assignment-order change that loses the old buffer and a guard-clause rewrite that turns a local variable read into a method call raising `NameError`. Sonicop also preserves the source encoding on write-back; RuboCop can write UTF-8 bytes under a non-UTF-8 magic comment. Each intentional incompatibility needs a minimal disk-level test and an entry in the divergence manifest. These safety exceptions are monitored differences, never omitted files.
|
|
216
50
|
|
|
217
|
-
|
|
51
|
+
For Ruby, both `-a` and `-A` exit unsuccessfully in RuboCop 1.89.0 on the source that also blocks its lint run. There is no complete corrected reference tree, so full-corpus autocorrect equality is unmeasured rather than assumed.
|
|
218
52
|
|
|
219
|
-
|
|
220
|
-
its 727 differences:
|
|
53
|
+
On the four public corpora with complete corrected trees, a second Sonicop `-A` pass changed **0 files**. Syntax checks of files changed by either tool found **0 candidate-only invalid Ruby files**; RuboCop alone produced invalid Ruby in 1 file of its own tree, 2 Rails files and 3 Mastodon files. A partial Sonicop `-a` run over Ruby was stopped during a large conversion table; among the 206 changed Ruby files that were valid before correction, **0 became invalid**. This partial result does not stand in for a complete Ruby autocorrect comparison.
|
|
221
54
|
|
|
222
|
-
|
|
223
|
-
|---|---:|---|
|
|
224
|
-
| in files one tool calls a syntax error | 635 | the recovery difference above |
|
|
225
|
-
| `Style/RedundantParentheses` in one file | 77 | RuboCop's defect, see below |
|
|
226
|
-
| everything else | 15 | |
|
|
227
|
-
|
|
228
|
-
The last 15 are the honest remainder. Ten of them sit in TRICK entries or encoding fixtures —
|
|
229
|
-
`Layout/SpaceInsideParens` five times in one obfuscated program, `Layout/TrailingWhitespace` and
|
|
230
|
-
`Layout/LeadingCommentSpace` on a UTF-16 fixture that holds no BOM. Three are the `"…"%[…]` lexing
|
|
231
|
-
gap described under *Grammar gaps*. That leaves two — `Lint/NestedMethodDefinition` on
|
|
232
|
-
`def (obj.bar = Object.new).baz` and `Style/MixinUsage` on a top-level `include RbConfig` — which
|
|
233
|
-
have not been investigated.
|
|
234
|
-
|
|
235
|
-
Naming that number is the point. It was 138 before the cop fixes that landed with the 609th cop, and
|
|
236
|
-
every step down came from taking one shape at a time rather than from a general improvement.
|
|
237
|
-
|
|
238
|
-
### Grammar gaps
|
|
239
|
-
|
|
240
|
-
Where tree-sitter rejects code that Ruby accepts, Sonicop reports a syntax error and — following the
|
|
241
|
-
rule above — reports nothing else for that file. One such gap costs every offense in the file at
|
|
242
|
-
once, which makes them worth hunting: fixing eight lexer rules in the grammar fork took ruby/ruby
|
|
243
|
-
from 24 affected files to 5, and cut the offenses Sonicop was missing there by more than sevenfold.
|
|
244
|
-
What is left moves with how many cops are implemented — one unparseable file costs every offense
|
|
245
|
-
every cop would have reported in it — so the shortfall grows as coverage does.
|
|
246
|
-
|
|
247
|
-
What remains are constructs whose ambiguity Ruby resolves with information a grammar does not have.
|
|
248
|
-
`$a?0:1` is the clearest: whether `?0` is a character literal or the start of a ternary depends on
|
|
249
|
-
whether the token before it completed an expression, which in turn depends on knowing that `a` is a
|
|
250
|
-
local variable rather than a method call.
|
|
251
|
-
|
|
252
|
-
A gap does not have to reject the file to cost offenses. `"%3d %s"%[l+1, line]` parses, but not as
|
|
253
|
-
Ruby reads it: a `%` written against a string literal can only be the operator, while tree-sitter
|
|
254
|
-
takes `%[…]` for a percent-literal string and yields two adjacent strings instead of a binary
|
|
255
|
-
operation. Nothing downstream can recover it — the operator node the cop would report on is not in
|
|
256
|
-
the tree — so `Layout/SpaceAroundOperators` misses both the `%` and the `+` nested inside the
|
|
257
|
-
brackets. Three offenses on ruby/ruby come from this, all on one line of `libexec/erb`.
|
|
258
|
-
|
|
259
|
-
### Encoding — the one deliberate difference
|
|
260
|
-
|
|
261
|
-
RuboCop's autocorrect ends in a plain `File.write`, so a corrected Shift_JIS file is written back as
|
|
262
|
-
UTF-8 while its magic comment still claims Shift_JIS. The result no longer loads: read it back as
|
|
263
|
-
cp932 and you get mojibake, not the source you had.
|
|
264
|
-
|
|
265
|
-
**Sonicop writes the correction back in the encoding the file declares**, and refuses to write at all
|
|
266
|
-
when the correction holds a character that encoding cannot represent, leaving the file untouched
|
|
267
|
-
rather than substituting something else. Drop-in compatibility reaches a long way, but not as far as
|
|
268
|
-
reproducing data loss on purpose. This is the only place Sonicop knowingly differs, and it only
|
|
269
|
-
shows up on files that declare a non-UTF-8 encoding — 8 of the 18,251 files measured here.
|
|
270
|
-
|
|
271
|
-
A source declaring `ASCII-8BIT` or `binary` needs no such treatment: Ruby measures it one byte at a
|
|
272
|
-
time, so Sonicop reads it that way too and the bytes go back out unchanged.
|
|
273
|
-
|
|
274
|
-
## Differences that should not be closed
|
|
275
|
-
|
|
276
|
-
Everything above records a place Sonicop has not reached. This section records the opposite: a
|
|
277
|
-
difference where RuboCop is the one that is wrong, and matching it would mean discarding a correct
|
|
278
|
-
report. The two are worth separating, because a table that mixes them reads as though every
|
|
279
|
-
difference counts against the port.
|
|
280
|
-
|
|
281
|
-
On ruby/ruby's `test/-ext-/bignum/test_pack.rb`, `Style/RedundantParentheses` finds 3 offenses in
|
|
282
|
-
RuboCop and 79 in Sonicop. The 76 are real: they are `(-n)`, `(-n+1)` and `(+n-1)` written as method
|
|
283
|
-
arguments, the same shape RuboCop reports elsewhere in the same file and in a file of its own.
|
|
284
|
-
|
|
285
|
-
RuboCop drops them because of an unrelated line further down. Truncating the file shows it exactly:
|
|
286
|
-
|
|
287
|
-
```
|
|
288
|
-
first 82 lines + a closing end → RuboCop reports 8
|
|
289
|
-
first 83 lines + a closing end → RuboCop reports 0 ← the 8 already reported are withdrawn
|
|
290
|
-
line 83 with \xFF changed to \x00 → RuboCop reports 9
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
Line 83 is `assert_equal([-1, "\xFF"], Bug::Bignum.test_pack((-0x0FF), 1, 1, 0, BIG_ENDIAN))`, in a
|
|
294
|
-
file whose first line is `# coding: ASCII-8BIT`. Reading that far makes RuboCop abandon
|
|
295
|
-
`Style/RedundantParentheses` for the whole file, silently:
|
|
296
|
-
|
|
297
|
-
- it is specific to that cop — `Style/StringLiterals` still reports its 10 offenses in the same file
|
|
298
|
-
- it is not the cop-error path — `--raise-cop-error` says nothing
|
|
299
|
-
- the file is inspected — the run reports `inspected: 1` and simply finds no offense of that cop
|
|
300
|
-
- it is not a parse failure — neither tool emits `Lint/Syntax`, and `ruby -c` accepts the file
|
|
301
|
-
- a synthesised minimum does not reproduce it; the surrounding file is part of the trigger
|
|
302
|
-
|
|
303
|
-
Sonicop reports all 79. Bringing that down to 3 would mean suppressing correct output to match a
|
|
304
|
-
defect, so the difference stands as it is.
|
|
305
|
-
|
|
306
|
-
## Limits
|
|
307
|
-
|
|
308
|
-
A clean run is a property of the corpora, not a general claim. `known_divergences.yml` carries the
|
|
309
|
-
current list of what these corpora never exercise; the main ones are extension plugins and Windows
|
|
310
|
-
line endings. Cops that never fire contribute nothing to a match count, and their silence is
|
|
311
|
-
indistinguishable from agreement.
|
|
312
|
-
|
|
313
|
-
Non-default configuration values used to be the largest of those gaps, and are now measured. Every
|
|
314
|
-
one of the 111 cops carrying an `Enforced*` setting was switched to a non-default value at once —
|
|
315
|
-
`Layout/SpaceInsideParens` to `space`, `Style/HashSyntax` to `hash_rockets` with
|
|
316
|
-
`EnforcedShorthandSyntax: always`, and so on — and rubocop/rubocop re-run against RuboCop under the
|
|
317
|
-
same file. Of 622,317 reference offenses, **99.99% match**, and 84 of the 96 cops that fired match
|
|
318
|
-
exactly.
|
|
319
|
-
|
|
320
|
-
| Cop | Offenses differing | |
|
|
321
|
-
|---|---:|---|
|
|
322
|
-
| `Layout/HashAlignment` | 35 | **RuboCop's own bug**, see below |
|
|
323
|
-
| `Style/BlockDelimiters` | 17 | |
|
|
324
|
-
| `Style/ConditionalAssignment` | 12 | |
|
|
325
|
-
| `Style/MixinGrouping` | 8 | |
|
|
326
|
-
| `Layout/SpaceInsideStringInterpolation` | 6 | |
|
|
327
|
-
| `Style/NumericPredicate` | 5 | |
|
|
328
|
-
| `Style/PercentQLiterals` | 4 | |
|
|
329
|
-
| `Naming/VariableName` | 3 | |
|
|
330
|
-
| `Lint/SymbolConversion` | 3 | |
|
|
331
|
-
| `Layout/SpaceInsideParens` | 1 | |
|
|
332
|
-
|
|
333
|
-
`Layout/HashAlignment`'s 35 are not a defect here. Under `EnforcedHashRocketStyle: separator`
|
|
334
|
-
RuboCop raises `Parser::ClobberingError` on `spec/rubocop/config_loader_spec.rb` — three times, at
|
|
335
|
-
`1111:35`, `1130:38` and `1217:35` — and reports nothing for the hashes it crashed on. Sonicop
|
|
336
|
-
inspects them and reports. Every one of the 35 sits in that file between lines 1114 and 1234.
|
|
337
|
-
Reproduce with:
|
|
55
|
+
## How to reproduce
|
|
338
56
|
|
|
339
57
|
```bash
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
One value per cop is not the whole configuration space — a cop with four supported styles is
|
|
344
|
-
measured at two of them — so this is a floor, not a ceiling. It does settle the shape of the old
|
|
345
|
-
limit: a cop is no longer allowed to be half absent and still count as matching.
|
|
58
|
+
# 固定した各コーパスで、先に対象パスの集合を比較する。
|
|
59
|
+
rubocop --force-default-config --cache false -L
|
|
60
|
+
sonicop --force-default-config -L
|
|
346
61
|
|
|
347
|
-
|
|
348
|
-
default
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
fires on these corpora contributes nothing either way — and the residue is not yet zero. The figures
|
|
352
|
-
for that run are tracked in `known_divergences.yml` rather than here, because they are still moving.
|
|
62
|
+
# 検査数を確認してから、JSON の位置と全項目を比較する。
|
|
63
|
+
rubocop --force-default-config --cache false -f json . > rubocop.json
|
|
64
|
+
sonicop --force-default-config --cache false -f json . > sonicop.json
|
|
65
|
+
```
|
|
353
66
|
|
|
354
|
-
|
|
355
|
-
inputs written to break it. Until that lands, this document records what was measured.
|
|
67
|
+
Use the `migrate-rubocop` measurement scripts for the actual comparison. Ruby needs their chunked `rcfull.py` reference. `fullfmt.sh` checks a clean source and compares copies, including both safe and unsafe correction. RuboCop 1.89.0 is the oracle for these conformance results even when a newer RuboCop is installed for timing.
|
|
356
68
|
|
|
357
|
-
|
|
69
|
+
The real-code corpora cannot cover every branch: some cops are disabled by default, and some syntax shapes do not occur. The upstream spec fixture suite supplies deliberately constructed cases; README's *Exercised* column keeps coverage separate from correctness. The goal remains **Cops = Exercised = Exact match** in every department. The last complete spec sweep (2026-08-28) exercised 609 and matched 579 exactly, so that goal is still open. The table must be regenerated by `scripts/conformance_table.rb` from measured JSON, never edited by hand.
|
data/README.ja.md
CHANGED
|
@@ -51,6 +51,8 @@ sonicop --show-cops
|
|
|
51
51
|
|
|
52
52
|
全 609 Cop を両者で有効にし、本家の spec が供給する 37,491 ケースを、それぞれの spec が指定した `TargetRubyVersion` で走らせて比較した結果です。**完全一致**とは、その Cop の offense が位置・メッセージ・重大度・修正可否まで 1 件残らず一致し、どちらにも余りがないことを指します。
|
|
53
53
|
|
|
54
|
+
以下は 2026-08-28 の最後の全件掃引です。その後の個別修正はこの数値に織り込んでいないため、現在すべての Cop が完全一致したという意味ではありません。
|
|
55
|
+
|
|
54
56
|
<!-- conformance:start -->
|
|
55
57
|
| デパートメント | Cop 数 | 検証済み | 完全一致 | 相違 |
|
|
56
58
|
|---|---:|---:|---:|---:|
|
|
@@ -131,7 +133,7 @@ sonicop --show-cops
|
|
|
131
133
|
|
|
132
134
|
## 設定
|
|
133
135
|
|
|
134
|
-
|
|
136
|
+
検査するファイルごとに `.rubocop.yml` を解決するため、サブディレクトリ設定も cop に適用します。対象ファイルの探索には、指定したディレクトリの設定を配下全体に使います。入れ子の `Include`/`Exclude` を探索に反映するには、そのディレクトリを別に指定してください。この挙動は RuboCop と一致します。ローカル/HTTPS の `inherit_from`、`inherit_gem`、`inherit_mode`、`AllCops/DisabledByDefault`、`Include`/`Exclude`、Cop ごとの `Enabled`、`Exclude`、`Severity`、`Safe`、`SafeAutoCorrect` と設定値に対応します。宣言されたプラグイン由来の Cop は「認識済み・未実装」として受理し、Ruby プラグインコード自体は実行しません。リモート設定のリクエストには 30 秒のネットワークタイムアウトを設け、応答は 1 件あたり 5 MiB に制限します。
|
|
135
137
|
|
|
136
138
|
```yaml
|
|
137
139
|
inherit_from: .rubocop_todo.yml
|
|
@@ -163,45 +165,37 @@ Cop の*名前*は検査されます。設定ファイルに未知の Cop 名が
|
|
|
163
165
|
|
|
164
166
|
## 適合性
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
sonicop は仕様とする RuboCop 1.89.0 の Cop を **609 / 609** 実装しています。固定した 5 つの Ruby プロジェクト(対象 18,251 ファイル)で、対象パスの集合は両者で一致します。既定設定の JSON は RuboCop 自身(offense 5,766 件)、Rails(167,760 件)、Mastodon(15,286 件)で、位置と全項目まで一致しました。Homebrew の差は `Lint/Syntax` のエラー復帰後の位置だけで、構文エラーと判定する 569 ファイルの集合は一致します。Ruby にはパーサと Cop の差が残るため、完全一致には数えません。
|
|
167
169
|
|
|
168
|
-
|
|
170
|
+
autocorrect は両者が別々の複製を修正した結果をバイト単位で比較します。本家が有効な Ruby を構文エラーに変える、代入順によって値を失う、ローカル変数の参照を `NameError` になるメソッド呼び出しへ変える場合は、安全のため意図的に一致させません。既知差分は[マニフェスト](tests/conformance/known_divergences.yml)に修正後ファイルのハッシュで固定しています。本家が最後まで処理できる 4 コーパスでは、`-a`・`-A` とも新規差分は 0 件です。Ruby は本家が途中で停止するため、完全な修正後ツリーを比較できません。Homebrew の修正後ツリーは両モードともバイト単位で一致します。
|
|
169
171
|
|
|
170
|
-
|
|
172
|
+
コーパスのコミット、版、offense の比較項目、実測値と限界は [CONFORMANCE.md](CONFORMANCE.md) に記録しています。[Cop の適合表](#cop-conformance)は本家 spec の入力で 609 Cop すべてを発火させる別の計測で、完全一致は 579 Cop、残る 30 Cop は未達です。
|
|
171
173
|
|
|
172
174
|
## 性能
|
|
173
175
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
両者とも既定の全 Cop で走らせています。**同じ 394 Cop** が名前まで一致しているため、どちらも絞る必要がなく、素の実行がそのまま対等な比較になります。(394 は 609 から `Enabled: pending` の 159 個と `Enabled: false` の 56 個を除いた残りで、既定の実行はどちらの群にも届きません。)各値は暖機後 2 回の最速値です。
|
|
177
|
-
|
|
178
|
-
| コーパス | ファイル | offense | RuboCop 並列 | Sonicop 並列 | RuboCop 単一 | Sonicop 単一 |
|
|
179
|
-
|---|---:|---:|---:|---:|---:|---:|
|
|
180
|
-
| rubocop/rubocop | 1,780 | 5,826 | 10.85 秒 | **1.53 秒** | 41.44 秒 | **8.96 秒** |
|
|
181
|
-
| mastodon/mastodon | 3,292 | 15,293 | 22.59 秒 | **3.04 秒** | 34.74 秒 | **6.86 秒** |
|
|
182
|
-
| Homebrew/brew | 2,296 | 51,527 | 13.56 秒 | **2.21 秒** | 40.36 秒 | **6.51 秒** |
|
|
183
|
-
| rails/rails | 3,562 | 168,615 | 32.90 秒 | **8.84 秒** | 85.71 秒 | **19.17 秒** |
|
|
184
|
-
| ruby/ruby | 7,477 | 765,975 | 86.89 秒 | **15.59 秒** | 193.59 秒 | **37.98 秒** |
|
|
185
|
-
|
|
186
|
-
差は並列で 3.7〜7.4 倍、単一プロセスで 4.5〜6.2 倍と幅があり、1 コーパスでは代表できません。**単一プロセスの列を読み、並列は目安として扱ってください。** 同じ 2 つのバイナリを 1 日に 3 回測ったところ、単一プロセスの値は毎回 16% 以内に収まったのに対し、RuboCop 自身のツリーでの並列の倍率は、マシンが他に何をしていたかだけで 3.3 倍から 9.2 倍まで動きました。単一プロセスはエンジンを測っていますが、並列はエンジンに加えて「その実行でスケジューリングがそのツリーにどれだけ噛み合ったか」を測っています。
|
|
176
|
+
2026-09-27 に Apple M2(8 コア)上で、最新安定版 Ruby 4.0.7 と RuboCop 1.91.0 を使って測定しました。これは新しい gem との速度比較です。[適合性](#適合性)の仕様は RuboCop 1.89.0 のままです。RuboCop 1.91.0 の Cop は 613 個、sonicop が実装する 1.89.0 の Cop は 609 個です。既定有効は両者 394 個ですが、互いに 1 個ずつ名前が異なります。速度計測では RuboCop から `Lint/CopDirectiveSyntax`、sonicop から `Style/DoubleCopDisableDirective` を除き、**共通の既定有効 393 Cop** に揃えました。
|
|
187
177
|
|
|
188
|
-
|
|
178
|
+
両者に `--force-default-config` を付け、対象パス集合を先に照合しています。Ruby では本家を途中で停止させる 2 ファイルを**両者から**除いた複製を使い、7,466 対象のうち 7,464 ファイルを測りました。各条件はキャッシュを空にして 2 回走らせた最速値です。並列の RuboCop は `--parallel --cache true` と毎回新しい実パスのキャッシュ、並列の sonicop も毎回新しいキャッシュを使います。単一プロセスでは両者 `--cache false`、sonicop に `--no-parallel` を指定しました。毎回 exit status と検査ファイル数を確認しました。
|
|
189
179
|
|
|
190
|
-
|
|
180
|
+
| コーパス | リビジョン | ファイル | 本家 offense | sonicop offense | 本家 並列 | sonicop 並列 | 本家 単一 | sonicop 単一 |
|
|
181
|
+
|---|---|---:|---:|---:|---:|---:|---:|---:|
|
|
182
|
+
| rubocop/rubocop | `f009b33` | 1,765 | 5,501 | 5,766 | 17.02 s | 2.25 s | 158.62 s | 21.22 s |
|
|
183
|
+
| mastodon/mastodon | `fad3685` | 3,290 | 15,292 | 15,286 | 16.45 s | 2.42 s | 125.86 s | 22.36 s |
|
|
184
|
+
| Homebrew/brew | `38ee325` | 2,179 | 49,926 | 49,326 | 15.71 s | 2.37 s | 132.17 s | 20.27 s |
|
|
185
|
+
| rails/rails | `62b5458` | 3,551 | 167,899 | 167,760 | 32.84 s | 7.34 s | 300.27 s | 60.17 s |
|
|
186
|
+
| ruby/ruby | `3349f41` | 7,464 | 763,186 | 761,189 | 82.64 s | 16.64 s | 776.44 s | 113.71 s |
|
|
191
187
|
|
|
192
|
-
|
|
193
|
-
# RuboCop(並列・キャッシュは毎回空・既定の全 394 Cop)
|
|
194
|
-
root=$(mktemp -d /private/tmp/bench.XXXXXX)
|
|
195
|
-
rubocop --force-default-config --cache true --cache-root "$root" \
|
|
196
|
-
--no-color --parallel -f quiet
|
|
197
|
-
|
|
198
|
-
# Sonicop(キャッシュは毎回空)
|
|
199
|
-
sonicop --force-default-config --cache-root "$root" --format quiet
|
|
200
|
-
```
|
|
188
|
+
offense の列も、この速度計測と同じ条件で実測した値です。新しい RuboCop では 1.89.0 から Cop の挙動が変わり得るため、同じ名前の Cop を走らせても仕事量が同じとは限りません。検出数と時間を並べて読み、エンジンの比較には単一プロセスの列を優先してください。並列の実時間は OS のスケジューリングにも左右されます。各コーパスの測定前後のロードアベレージを下に記録します。
|
|
201
189
|
|
|
202
|
-
|
|
190
|
+
| コーパス | 開始時 | 終了時 | 記録した最大値 |
|
|
191
|
+
|---|---:|---:|---:|
|
|
192
|
+
| rubocop/rubocop | 16.38 | 19.28 | 43.68 |
|
|
193
|
+
| mastodon/mastodon | 19.28 | 18.65 | 26.13 |
|
|
194
|
+
| Homebrew/brew | 18.65 | 18.69 | 22.94 |
|
|
195
|
+
| rails/rails | 18.69 | 14.91 | 45.20 |
|
|
196
|
+
| ruby/ruby | 14.91 | 18.51 | 45.85 |
|
|
203
197
|
|
|
204
|
-
|
|
198
|
+
このマシンは測定中も負荷が高く、OS の別処理と競合していました。秒数はこの条件での実測値であり、静かな環境での下限値ではありません。
|
|
205
199
|
|
|
206
200
|
## 開発
|
|
207
201
|
|
data/README.md
CHANGED
|
@@ -51,6 +51,8 @@ sonicop --show-cops
|
|
|
51
51
|
|
|
52
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.
|
|
53
53
|
|
|
54
|
+
The table below is the last complete spec sweep, measured on 2026-08-28. Later targeted fixes have not been folded into these counts; the table is not a claim that every cop is now exact.
|
|
55
|
+
|
|
54
56
|
<!-- conformance:start -->
|
|
55
57
|
| Department | Cops | Exercised | Exact match | Diverging |
|
|
56
58
|
|---|---:|---:|---:|---:|
|
|
@@ -131,7 +133,7 @@ Key compatibility flags include `-l`, `-x`, `--only`, `--except`, `-s/--stdin`,
|
|
|
131
133
|
|
|
132
134
|
## Configuration
|
|
133
135
|
|
|
134
|
-
Sonicop resolves `.rubocop.yml` from each
|
|
136
|
+
Sonicop resolves `.rubocop.yml` from each inspected file, so nested configurations apply to its cops. For target discovery, a directory argument uses that directory's configuration across its walk, matching RuboCop; list a nested directory separately to apply its own `Include` and `Exclude` rules. 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.
|
|
135
137
|
|
|
136
138
|
```yaml
|
|
137
139
|
inherit_from: .rubocop_todo.yml
|
|
@@ -163,45 +165,37 @@ Cop *names* are checked: an unrecognised cop in a configuration file stops the r
|
|
|
163
165
|
|
|
164
166
|
## Conformance
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
Sonicop implements **609 / 609** cops from its RuboCop 1.89.0 specification. On five pinned Ruby projects (18,251 target files), both tools discover the same path sets. Their default-config JSON reports match offense by offense on RuboCop's own tree (5,766 offenses), Rails (167,760) and Mastodon (15,286). Homebrew differs only in `Lint/Syntax` recovery positions: both reject the same 569 files. Ruby still has parser and cop differences; it is not counted as exact.
|
|
167
169
|
|
|
168
|
-
|
|
170
|
+
Autocorrect is also compared on separate copies of each project. Some differences are deliberate safety decisions: RuboCop can write invalid Ruby, lose a value through assignment order, or turn a local-variable read into a `NameError`. Each known difference is pinned by corrected-file hashes in [the divergence manifest](tests/conformance/known_divergences.yml). For the pinned trees, `-a` has 0 new differences and `-A` has 0 new differences among the four corpora RuboCop can finish; Ruby's reference aborts before a complete correction tree exists. The corrected trees are byte-identical on Homebrew, while the other known differences remain visible in the gate.
|
|
169
171
|
|
|
170
|
-
|
|
172
|
+
[CONFORMANCE.md](CONFORMANCE.md) records the corpus commits, reference versions, exact offense fields, measured counts and limitations. The [cop conformance table](#cop-conformance) uses upstream spec cases to exercise all 609 cops; its 579 exact matches are a separate measurement and the remaining 30 are still open.
|
|
171
173
|
|
|
172
174
|
## Performance
|
|
173
175
|
|
|
174
|
-
Measured
|
|
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.
|
|
177
|
-
|
|
178
|
-
| Corpus | Files | Offenses | RuboCop parallel | Sonicop parallel | RuboCop single | Sonicop single |
|
|
179
|
-
|---|---:|---:|---:|---:|---:|---:|
|
|
180
|
-
| rubocop/rubocop | 1,780 | 5,826 | 10.85 s | **1.53 s** | 41.44 s | **8.96 s** |
|
|
181
|
-
| mastodon/mastodon | 3,292 | 15,293 | 22.59 s | **3.04 s** | 34.74 s | **6.86 s** |
|
|
182
|
-
| Homebrew/brew | 2,296 | 51,527 | 13.56 s | **2.21 s** | 40.36 s | **6.51 s** |
|
|
183
|
-
| rails/rails | 3,562 | 168,615 | 32.90 s | **8.84 s** | 85.71 s | **19.17 s** |
|
|
184
|
-
| ruby/ruby | 7,477 | 765,975 | 86.89 s | **15.59 s** | 193.59 s | **37.98 s** |
|
|
185
|
-
|
|
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.
|
|
176
|
+
Measured on 2026-09-27 with the latest stable Ruby 4.0.7 and RuboCop 1.91.0 on an Apple M2 (8 cores). This is a speed comparison with the newer gem; [conformance](#conformance) still uses RuboCop 1.89.0 as the behavioral specification. RuboCop 1.91.0 has 613 cops and Sonicop implements the 609 from 1.89.0. Each has 394 default-enabled cops, but one name differs in each set. For timing, RuboCop excludes `Lint/CopDirectiveSyntax` and Sonicop excludes `Style/DoubleCopDisableDirective`, leaving the **same 393 default-enabled cop names**.
|
|
187
177
|
|
|
188
|
-
|
|
178
|
+
Both use `--force-default-config`, and their target path sets were checked before timing. Ruby's two files that make RuboCop abort are omitted **from both tools** in the timing copy; that row covers 7,464 of its 7,466 targets. Times are the fastest of two cold runs per condition. Parallel RuboCop uses `--parallel --cache true` with a fresh real-path cache root for each run; parallel Sonicop uses its default parallel execution with a fresh root. Single-process runs use `--cache false` on both sides and `--no-parallel` on Sonicop. Exit status and inspected-file counts were checked for every run.
|
|
189
179
|
|
|
190
|
-
|
|
180
|
+
| Corpus | Revision | Files | RuboCop offenses | Sonicop offenses | RuboCop parallel | Sonicop parallel | RuboCop single | Sonicop single |
|
|
181
|
+
|---|---|---:|---:|---:|---:|---:|---:|---:|
|
|
182
|
+
| rubocop/rubocop | `f009b33` | 1,765 | 5,501 | 5,766 | 17.02 s | 2.25 s | 158.62 s | 21.22 s |
|
|
183
|
+
| mastodon/mastodon | `fad3685` | 3,290 | 15,292 | 15,286 | 16.45 s | 2.42 s | 125.86 s | 22.36 s |
|
|
184
|
+
| Homebrew/brew | `38ee325` | 2,179 | 49,926 | 49,326 | 15.71 s | 2.37 s | 132.17 s | 20.27 s |
|
|
185
|
+
| rails/rails | `62b5458` | 3,551 | 167,899 | 167,760 | 32.84 s | 7.34 s | 300.27 s | 60.17 s |
|
|
186
|
+
| ruby/ruby | `3349f41` | 7,464 | 763,186 | 761,189 | 82.64 s | 16.64 s | 776.44 s | 113.71 s |
|
|
191
187
|
|
|
192
|
-
|
|
193
|
-
# RuboCop, parallel, cold cache, its full default set of 394 cops
|
|
194
|
-
root=$(mktemp -d /private/tmp/bench.XXXXXX)
|
|
195
|
-
rubocop --force-default-config --cache true --cache-root "$root" \
|
|
196
|
-
--no-color --parallel -f quiet
|
|
197
|
-
|
|
198
|
-
# Sonicop, cold cache
|
|
199
|
-
sonicop --force-default-config --cache-root "$root" --format quiet
|
|
200
|
-
```
|
|
188
|
+
The offense columns are measured under these exact timing conditions. The newer RuboCop may have changed cop behavior since the 1.89.0 conformance reference, so equal cop names alone do not guarantee equal work. Read the offense counts alongside the times, and use the single-process column to judge engine cost: parallel wall time also depends on machine scheduling. The load average before and after each corpus is recorded below; high load can make absolute times and ratios move.
|
|
201
189
|
|
|
202
|
-
|
|
190
|
+
| Corpus | Before | After | Highest recorded sample |
|
|
191
|
+
|---|---:|---:|---:|
|
|
192
|
+
| rubocop/rubocop | 16.38 | 19.28 | 43.68 |
|
|
193
|
+
| mastodon/mastodon | 19.28 | 18.65 | 26.13 |
|
|
194
|
+
| Homebrew/brew | 18.65 | 18.69 | 22.94 |
|
|
195
|
+
| rails/rails | 18.69 | 14.91 | 45.20 |
|
|
196
|
+
| ruby/ruby | 14.91 | 18.51 | 45.85 |
|
|
203
197
|
|
|
204
|
-
|
|
198
|
+
The machine remained busy with other OS activity. These are observed times under that load, not an idle-machine lower bound.
|
|
205
199
|
|
|
206
200
|
## Development
|
|
207
201
|
|
data/lib/sonicop/version.rb
CHANGED
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.
|
|
4
|
+
version: 26.9.102
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Yohei
|
|
@@ -53,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
55
|
requirements: []
|
|
56
|
-
rubygems_version:
|
|
56
|
+
rubygems_version: 4.0.20
|
|
57
57
|
specification_version: 4
|
|
58
58
|
summary: A fast, native RuboCop-compatible Ruby linter and formatter
|
|
59
59
|
test_files: []
|