sonicop 26.9.100 → 26.9.102
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/Cargo.lock +42 -7
- data/Cargo.toml +5 -5
- data/README.ja.md +128 -233
- data/README.md +120 -238
- data/lib/sonicop/version.rb +1 -1
- data/src/config/paths.rs +10 -10
- data/src/diagnostic.rs +21 -6
- data/src/engine.rs +61 -36
- data/src/rules/layout/comment_indentation.rs +4 -4
- data/src/rules/layout/condition_position.rs +9 -9
- data/src/rules/layout/else_alignment.rs +4 -4
- data/src/rules/layout/empty_line_after_guard_clause.rs +4 -5
- data/src/rules/layout/empty_lines_around_body.rs +7 -1
- data/src/rules/layout/end_of_line.rs +46 -4
- data/src/rules/layout/hash_alignment.rs +4 -4
- data/src/rules/layout/indentation_width.rs +10 -10
- data/src/rules/layout/line_length.rs +14 -14
- data/src/rules/layout/multiline_block_layout.rs +22 -26
- data/src/rules/layout/rescue_ensure_alignment.rs +9 -10
- data/src/rules/layout/space_around_block_parameters.rs +8 -8
- data/src/rules/layout/space_around_keyword.rs +4 -4
- data/src/rules/layout/support.rs +30 -21
- data/src/rules/lint/ambiguity.rs +5 -4
- data/src/rules/lint/ambiguous_block_association.rs +2 -2
- data/src/rules/lint/debugger.rs +2 -1
- data/src/rules/lint/deprecated_open_ssl_constant.rs +3 -1
- data/src/rules/lint/duplicate_methods.rs +4 -0
- data/src/rules/lint/interpolation_check.rs +13 -46
- data/src/rules/lint/return_in_void_context.rs +4 -6
- data/src/rules/lint/variable_force.rs +20 -17
- data/src/rules/metrics/abc_size.rs +1 -1
- data/src/rules/metrics/complexity.rs +21 -15
- data/src/rules/metrics/locals.rs +6 -3
- data/src/rules/mod.rs +15 -7
- data/src/rules/style/case_like_if.rs +56 -24
- data/src/rules/style/date_time.rs +4 -4
- data/src/rules/style/guard_clause.rs +42 -8
- data/src/rules/style/hash_each_methods.rs +16 -17
- data/src/rules/style/hash_syntax.rs +2 -2
- data/src/rules/style/identical_conditional_branches.rs +5 -6
- data/src/rules/style/line_length_help.rs +5 -4
- data/src/rules/style/multiline_block_chain.rs +11 -2
- data/src/rules/style/redundant_begin.rs +4 -4
- data/src/rules/style/redundant_each.rs +11 -11
- data/src/rules/style/redundant_interpolation.rs +9 -11
- data/src/rules/style/redundant_parentheses.rs +17 -15
- data/src/rules/style/redundant_regexp_argument.rs +1 -1
- data/src/rules/style/safe_navigation.rs +15 -15
- data/src/rules/style/special_global_vars.rs +4 -4
- 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: 3ee92dd7275a249c8db40072683d08b20ffa8817cd429baf8b9c60f38b36aafe
|
|
4
|
+
data.tar.gz: 1f68e0627a4a12853fc7a34309a16fe1788c41fa251c57120a75e1728a1d84f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 022f75103a2e2f5815884412de648e658b32b3d7811bd714bf1a9625472b2e83b33d00f5d5d9f326abea9d8fe662bf7e92555fde904166a952892821cce11bc4
|
|
7
|
+
data.tar.gz: 4bf9bb1d6e191d578ba9cf9e6a034219d1e3dc784932987d8b0cf269c42cbebb8a222637711b5704ad0d9c344566ee3207fda5ecf5ad12ae45cfbd60172db36a
|
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/Cargo.lock
CHANGED
|
@@ -102,9 +102,9 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
|
|
|
102
102
|
|
|
103
103
|
[[package]]
|
|
104
104
|
name = "bitflags"
|
|
105
|
-
version = "2.13.
|
|
105
|
+
version = "2.13.2"
|
|
106
106
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
107
|
-
checksum = "
|
|
107
|
+
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
|
108
108
|
|
|
109
109
|
[[package]]
|
|
110
110
|
name = "blake3"
|
|
@@ -221,6 +221,12 @@ version = "0.8.7"
|
|
|
221
221
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
222
222
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
|
223
223
|
|
|
224
|
+
[[package]]
|
|
225
|
+
name = "core_detect"
|
|
226
|
+
version = "1.0.0"
|
|
227
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
228
|
+
checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
|
|
229
|
+
|
|
224
230
|
[[package]]
|
|
225
231
|
name = "cpufeatures"
|
|
226
232
|
version = "0.3.1"
|
|
@@ -279,11 +285,16 @@ checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
|
|
279
285
|
|
|
280
286
|
[[package]]
|
|
281
287
|
name = "encoding_rs"
|
|
282
|
-
version = "0.8.
|
|
288
|
+
version = "0.8.42"
|
|
283
289
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
284
|
-
checksum = "
|
|
290
|
+
checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679"
|
|
285
291
|
dependencies = [
|
|
286
292
|
"cfg-if",
|
|
293
|
+
"core_detect",
|
|
294
|
+
"multiversion_no_op",
|
|
295
|
+
"rustversion",
|
|
296
|
+
"scopeguard",
|
|
297
|
+
"simdutf8",
|
|
287
298
|
]
|
|
288
299
|
|
|
289
300
|
[[package]]
|
|
@@ -453,6 +464,12 @@ version = "2.8.3"
|
|
|
453
464
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
454
465
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|
455
466
|
|
|
467
|
+
[[package]]
|
|
468
|
+
name = "multiversion_no_op"
|
|
469
|
+
version = "1.0.0"
|
|
470
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
471
|
+
checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
|
|
472
|
+
|
|
456
473
|
[[package]]
|
|
457
474
|
name = "native-tls"
|
|
458
475
|
version = "0.2.18"
|
|
@@ -668,6 +685,12 @@ dependencies = [
|
|
|
668
685
|
"zeroize",
|
|
669
686
|
]
|
|
670
687
|
|
|
688
|
+
[[package]]
|
|
689
|
+
name = "rustversion"
|
|
690
|
+
version = "1.0.23"
|
|
691
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
692
|
+
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
|
693
|
+
|
|
671
694
|
[[package]]
|
|
672
695
|
name = "ryu"
|
|
673
696
|
version = "1.0.23"
|
|
@@ -692,6 +715,12 @@ dependencies = [
|
|
|
692
715
|
"windows-sys",
|
|
693
716
|
]
|
|
694
717
|
|
|
718
|
+
[[package]]
|
|
719
|
+
name = "scopeguard"
|
|
720
|
+
version = "1.2.0"
|
|
721
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
722
|
+
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
723
|
+
|
|
695
724
|
[[package]]
|
|
696
725
|
name = "security-framework"
|
|
697
726
|
version = "3.7.0"
|
|
@@ -784,9 +813,15 @@ version = "2.0.1"
|
|
|
784
813
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
785
814
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
|
786
815
|
|
|
816
|
+
[[package]]
|
|
817
|
+
name = "simdutf8"
|
|
818
|
+
version = "0.1.5"
|
|
819
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
820
|
+
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
|
821
|
+
|
|
787
822
|
[[package]]
|
|
788
823
|
name = "sonicop"
|
|
789
|
-
version = "26.9.
|
|
824
|
+
version = "26.9.102"
|
|
790
825
|
dependencies = [
|
|
791
826
|
"anyhow",
|
|
792
827
|
"assert_cmd",
|
|
@@ -892,8 +927,8 @@ checksum = "ca0d1bf6fdd806e43ae5198f82f527056d359def39e54e67a0f478ac09dac081"
|
|
|
892
927
|
|
|
893
928
|
[[package]]
|
|
894
929
|
name = "tree-sitter-ruby"
|
|
895
|
-
version = "26.
|
|
896
|
-
source = "git+https://github.com/owayo/tree-sitter-ruby.git?tag=v26.
|
|
930
|
+
version = "26.9.100"
|
|
931
|
+
source = "git+https://github.com/owayo/tree-sitter-ruby.git?tag=v26.9.100#710bc56bda9d8548dc5e8714a6418f2deb292e05"
|
|
897
932
|
dependencies = [
|
|
898
933
|
"cc",
|
|
899
934
|
"tree-sitter-language",
|
data/Cargo.toml
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "sonicop"
|
|
3
|
-
version = "26.9.
|
|
3
|
+
version = "26.9.102"
|
|
4
4
|
edition = "2024"
|
|
5
|
-
rust-version = "1.
|
|
5
|
+
rust-version = "1.98"
|
|
6
6
|
description = "A fast, native RuboCop-compatible Ruby linter and formatter"
|
|
7
7
|
license = "MIT"
|
|
8
8
|
repository = "https://github.com/owayo/sonicop"
|
|
@@ -18,7 +18,7 @@ path = "src/main.rs"
|
|
|
18
18
|
anyhow = "1.0.104"
|
|
19
19
|
blake3 = "1.8.7"
|
|
20
20
|
clap = { version = "4.6.6", features = ["derive", "wrap_help"] }
|
|
21
|
-
encoding_rs = "0.8.
|
|
21
|
+
encoding_rs = "0.8.42"
|
|
22
22
|
fs2 = "0.4.3"
|
|
23
23
|
globset = "0.4.20"
|
|
24
24
|
ignore = "0.4.33"
|
|
@@ -30,7 +30,7 @@ serde_yaml_ng = "0.10"
|
|
|
30
30
|
shell-words = "1.1.1"
|
|
31
31
|
tempfile = "3.27.0"
|
|
32
32
|
tree-sitter = "0.27.0"
|
|
33
|
-
tree-sitter-ruby = { git = "https://github.com/owayo/tree-sitter-ruby.git", tag = "v26.
|
|
33
|
+
tree-sitter-ruby = { git = "https://github.com/owayo/tree-sitter-ruby.git", tag = "v26.9.100" }
|
|
34
34
|
ureq = { version = "3.4.1", default-features = false, features = ["native-tls"] }
|
|
35
35
|
|
|
36
36
|
[dev-dependencies]
|
|
@@ -40,7 +40,7 @@ assert_cmd = "2.2.2"
|
|
|
40
40
|
blake3 = "1.8.7"
|
|
41
41
|
|
|
42
42
|
[profile.release]
|
|
43
|
-
lto =
|
|
43
|
+
lto = true
|
|
44
44
|
codegen-units = 1
|
|
45
45
|
strip = true
|
|
46
46
|
|