rubocop-tablecop 0.2.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 51eef2c0d5feffe748c3522a755b445f04c3a4b3375ae0aea80fed3abac7e3c6
4
+ data.tar.gz: d970c3c03b7cf9e4dc01c3184e276e79aaf7e88651d651f5f7d0b444ba7bad21
5
+ SHA512:
6
+ metadata.gz: 19bed6636d3be68d97d506d660dab28f2470eb2e6f3dc94312809a32ee7909a66d914e3a1995e65278f38e4c8b21c4365cde21839528be48748dad27661bde65
7
+ data.tar.gz: e0c333e488389fee8c0cace9506b429e3eff5fed20b503ec2972ecbe5030c2b4791ba91cd5f8518e90338d48c93064a4c28b83e0f1507bf57bd7877d8be60b4d
data/CHANGELOG.md ADDED
@@ -0,0 +1,63 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.0] - 2025-12-13
9
+
10
+ ### Changed
11
+
12
+ - **BREAKING**: Renamed gem from `tablecop` to `rubocop-tablecop` for RuboCop plugin auto-discovery
13
+ - **BREAKING**: Minimum RuboCop version increased from 1.50 to 1.72 (required for plugin system)
14
+ - Migrated to RuboCop's plugin system using `lint_roller`
15
+ - Version module relocated to `RuboCop::Tablecop::Version::STRING` (backwards-compatible `VERSION` constant retained)
16
+
17
+ ### Added
18
+
19
+ - `RuboCop::Tablecop::Plugin` class for native plugin integration
20
+ - Can now use `plugins: [rubocop-tablecop]` in `.rubocop.yml` instead of `require:`
21
+
22
+ ### Fixed
23
+
24
+ - Removed unused variable warning in `AlignAssignments` cop
25
+
26
+ ### Migration
27
+
28
+ Update your configuration:
29
+
30
+ ```yaml
31
+ # Before (.rubocop.yml)
32
+ require:
33
+ - tablecop
34
+
35
+ # After (.rubocop.yml)
36
+ plugins:
37
+ - rubocop-tablecop
38
+ ```
39
+
40
+ Update your Gemfile:
41
+
42
+ ```ruby
43
+ # Before
44
+ gem "tablecop"
45
+
46
+ # After
47
+ gem "rubocop-tablecop", "~> 0.2"
48
+ ```
49
+
50
+ ## [0.1.0] - 2025-12-10
51
+
52
+ ### Added
53
+
54
+ - `Tablecop/CondenseWhen` - Condenses multi-line `when` clauses to single lines and aligns `then` keywords
55
+ - `Tablecop/AlignMethods` - Aligns contiguous single-line method definitions on the `=` operator
56
+ - `Tablecop/AlignAssignments` - Aligns consecutive assignment statements on the `=` operator
57
+ - `Tablecop/SafeEndlessMethod` - Converts multi-line methods to endless or traditional one-liner form, avoiding RuboCop's known bugs
58
+ - Safe default configuration that disables known-buggy RuboCop cops:
59
+ - `Style/EndlessMethod` (heredoc destruction, rescue orphaning, module_eval failures)
60
+ - `Style/DoubleNegation` (semantically wrong: `!!false` ≠ `!false.nil?`)
61
+ - `Style/HashExcept` (breaks mixed symbol/string key handling)
62
+ - `Layout/ExtraSpacing` with `ForceEqualSignAlignment` (infinite loops with heredocs)
63
+ - Documentation of known RuboCop autocorrect bugs in `docs/known-issues.md`
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Joseph Wecker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,433 @@
1
+ # rubocop-tablecop
2
+
3
+ A RuboCop plugin for table-like, condensed Ruby formatting. Enforces vertical alignment and single-line expressions where they improve readability, while avoiding RuboCop's known autocorrect bugs.
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "rubocop-tablecop"
11
+ ```
12
+
13
+ Then run:
14
+
15
+ ```bash
16
+ bundle install
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ Add to your `.rubocop.yml`:
22
+
23
+ ```yaml
24
+ # RuboCop 1.72+ (plugin system - recommended)
25
+ plugins:
26
+ - rubocop-tablecop
27
+
28
+ # Or for older RuboCop versions
29
+ require:
30
+ - rubocop-tablecop
31
+ ```
32
+
33
+ The plugin requires **RuboCop >= 1.72**.
34
+
35
+ ## Cops
36
+
37
+ ### Tablecop/AlignAssignments
38
+
39
+ Aligns consecutive assignment statements on the `=` operator for improved readability.
40
+
41
+ **Enabled by default:** Yes
42
+
43
+ **Supports autocorrect:** Yes
44
+
45
+ **What it does:**
46
+ - Aligns consecutive assignments at the same indentation level
47
+ - Handles simple assignments (`=`), compound assignments (`||=`, `&&=`, `+=`, etc.), and constant assignments
48
+ - Skips lines containing heredocs to avoid infinite loops with other cops
49
+ - Respects `Layout/LineLength` configuration
50
+
51
+ **Examples:**
52
+
53
+ ```ruby
54
+ # bad
55
+ x = 1
56
+ foo = 2
57
+ barbaz = 3
58
+
59
+ # good
60
+ x = 1
61
+ foo = 2
62
+ barbaz = 3
63
+ ```
64
+
65
+ ```ruby
66
+ # bad (compound operators)
67
+ data ||= attrs
68
+ options = default
69
+ config &&= fallback
70
+
71
+ # good
72
+ data ||= attrs
73
+ options = default
74
+ config &&= fallback
75
+ ```
76
+
77
+ **Limitations:**
78
+ - Skips assignments inside blocks
79
+ - Skips multi-assignment (`a, b = ...`)
80
+ - Skips assignments containing heredocs
81
+ - Only aligns assignments on consecutive lines at the same indentation level
82
+
83
+ ### Tablecop/AlignMethods
84
+
85
+ Aligns contiguous single-line method definitions so their bodies start at the same column.
86
+
87
+ **Enabled by default:** Yes
88
+
89
+ **Supports autocorrect:** Yes
90
+
91
+ **What it does:**
92
+ - Aligns on `=` for endless methods
93
+ - Aligns traditional one-liners as if they had an invisible `=`
94
+ - Works with both instance and singleton methods
95
+ - Respects `Layout/LineLength` configuration
96
+
97
+ **Examples:**
98
+
99
+ ```ruby
100
+ # bad
101
+ def foo = 1
102
+ def barbaz = 2
103
+
104
+ # good
105
+ def foo = 1
106
+ def barbaz = 2
107
+ ```
108
+
109
+ ```ruby
110
+ # bad (mixed endless and traditional)
111
+ def foo = 1
112
+ def bar() 2 end
113
+
114
+ # good
115
+ def foo = 1
116
+ def bar() 2 end
117
+ ```
118
+
119
+ ```ruby
120
+ # bad (singleton methods)
121
+ def self.x = 1
122
+ def self.longer_name = 2
123
+
124
+ # good
125
+ def self.x = 1
126
+ def self.longer_name = 2
127
+ ```
128
+
129
+ **Limitations:**
130
+ - Only aligns methods on consecutive lines at the same indentation level
131
+ - Only processes single-line methods
132
+
133
+ ### Tablecop/CondenseWhen
134
+
135
+ Condenses multi-line `when` clauses to single lines using the `then` keyword and aligns them vertically.
136
+
137
+ **Enabled by default:** Yes
138
+
139
+ **Supports autocorrect:** Yes
140
+
141
+ **What it does:**
142
+ - Converts multi-line `when` clauses to single-line format with `then`
143
+ - Aligns `then` keywords across sibling `when` clauses for table-like appearance
144
+ - Only condenses when the result fits within `Layout/LineLength`
145
+ - Preserves complex bodies that shouldn't be condensed
146
+
147
+ **Examples:**
148
+
149
+ ```ruby
150
+ # bad
151
+ case foo
152
+ when 1
153
+ "one"
154
+ when 200
155
+ "two hundred"
156
+ end
157
+
158
+ # good
159
+ case foo
160
+ when 1 then "one"
161
+ when 200 then "two hundred"
162
+ end
163
+ ```
164
+
165
+ ```ruby
166
+ # bad
167
+ case status
168
+ when :pending
169
+ handle_pending
170
+ when :approved
171
+ handle_approved
172
+ end
173
+
174
+ # good
175
+ case status
176
+ when :pending then handle_pending
177
+ when :approved then handle_approved
178
+ end
179
+ ```
180
+
181
+ **What it skips:**
182
+ - Multi-statement bodies
183
+ - Bodies with heredocs
184
+ - Bodies with multi-line strings
185
+ - Bodies with comments between `when` and body
186
+ - Complex control flow (multi-line `if`/`case`)
187
+ - Multi-statement blocks
188
+ - Cases where condensing would exceed line length
189
+
190
+ **Example of what's left alone:**
191
+
192
+ ```ruby
193
+ # left alone (multiple statements)
194
+ case foo
195
+ when 1
196
+ do_something
197
+ do_something_else
198
+ end
199
+
200
+ # left alone (heredoc)
201
+ case foo
202
+ when 1
203
+ <<~MSG
204
+ Hello
205
+ MSG
206
+ end
207
+ ```
208
+
209
+ ### Tablecop/SafeEndlessMethod
210
+
211
+ Converts multi-line single-expression methods to single-line form, avoiding all the known bugs in RuboCop's `Style/EndlessMethod`.
212
+
213
+ **Enabled by default:** Yes
214
+
215
+ **Supports autocorrect:** Yes
216
+
217
+ **What it does:**
218
+ - Converts simple multi-line methods to endless method syntax (`def foo = expr`)
219
+ - Falls back to traditional one-liner (`def foo() expr end`) for methods with modifier-if/unless that call other methods
220
+ - Avoids RuboCop's `Style/EndlessMethod` bugs:
221
+ - Heredoc destruction
222
+ - Rescue clause orphaning
223
+ - module_eval context failures
224
+ - Modifier-if with dynamic method failures
225
+
226
+ **Examples:**
227
+
228
+ ```ruby
229
+ # bad
230
+ def foo
231
+ 42
232
+ end
233
+
234
+ # good
235
+ def foo = 42
236
+ ```
237
+
238
+ ```ruby
239
+ # bad
240
+ def calculate(x, y)
241
+ x + y
242
+ end
243
+
244
+ # good
245
+ def calculate(x, y) = x + y
246
+ ```
247
+
248
+ ```ruby
249
+ # bad (modifier-if with method call)
250
+ def clear!
251
+ data_layer.clear! if data_layer.respond_to?(:clear!)
252
+ end
253
+
254
+ # good (uses traditional one-liner to avoid bugs)
255
+ def clear!() data_layer.clear! if data_layer.respond_to?(:clear!) end
256
+ ```
257
+
258
+ **What it skips:**
259
+ - Methods already on a single line
260
+ - Methods with multiple statements
261
+ - Methods with heredocs
262
+ - Methods with rescue/ensure clauses
263
+ - Setter methods (ending with `=`)
264
+ - Methods with multi-statement blocks
265
+ - Methods with complex control flow (multi-line `if`/`case`)
266
+ - Methods where condensing would exceed line length
267
+
268
+ ## Built-in Cop Configuration
269
+
270
+ Tablecop sets opinionated defaults for RuboCop's built-in cops to achieve a table-like, condensed style. These are configured in `config/default.yml` and automatically applied when you use the plugin.
271
+
272
+ ### Disabled Cops (Conflicts)
273
+
274
+ These cops are disabled because they conflict with Tablecop's alignment features:
275
+
276
+ ```yaml
277
+ # Conflicts with Tablecop/AlignAssignments
278
+ Layout/SpaceAroundOperators:
279
+ Enabled: false # Enforces exactly one space, undoes alignment
280
+
281
+ # Conflicts with Tablecop/SafeEndlessMethod
282
+ Style/SingleLineDoEndBlock:
283
+ Enabled: false # Would reformat SafeEndlessMethod output
284
+
285
+ Style/SingleLineMethods:
286
+ Enabled: false # Would reformat SafeEndlessMethod output
287
+ ```
288
+
289
+ ### Disabled Cops (Known Bugs)
290
+
291
+ These cops have critical autocorrect bugs and are replaced by safer Tablecop equivalents:
292
+
293
+ ```yaml
294
+ # REPLACED by Tablecop/SafeEndlessMethod
295
+ Style/EndlessMethod:
296
+ Enabled: false # Destroys heredocs, orphans rescue clauses
297
+
298
+ Style/AmbiguousEndlessMethodDefinition:
299
+ Enabled: false # Related to EndlessMethod issues
300
+
301
+ # BUGS: Creates syntax errors and silent logic bugs
302
+ Style/DoubleNegation:
303
+ Enabled: false # !!false ≠ !false.nil?
304
+
305
+ Style/HashExcept:
306
+ Enabled: false # Breaks mixed symbol/string key handling
307
+ ```
308
+
309
+ ### Layout Configuration
310
+
311
+ ```yaml
312
+ Layout/HashAlignment:
313
+ EnforcedHashRocketStyle: table
314
+ EnforcedColonStyle: table
315
+ EnforcedLastArgumentHashStyle: always_inspect
316
+
317
+ Layout/ExtraSpacing:
318
+ AllowForAlignment: true
319
+ AllowBeforeTrailingComments: true
320
+ ForceEqualSignAlignment: false # Would cause infinite loops with heredocs
321
+
322
+ Layout/FirstArrayElementIndentation:
323
+ EnforcedStyle: consistent # Not aligned to brackets
324
+
325
+ Layout/FirstArgumentIndentation:
326
+ EnforcedStyle: consistent # Not aligned to parens
327
+
328
+ Layout/SpaceInsideHashLiteralBraces:
329
+ EnforcedStyle: space # { foo: 1 } not {foo: 1}
330
+ ```
331
+
332
+ See `config/default.yml` for the complete configuration.
333
+
334
+ ## Known Issues
335
+
336
+ RuboCop has several autocorrect bugs that can destroy code. Tablecop's default configuration disables or works around these where possible. See [docs/known-issues.md](docs/known-issues.md) for detailed documentation of:
337
+
338
+ | Bug | Cop | Impact | Tablecop Mitigation |
339
+ |-----|-----|--------|---------------------|
340
+ | Heredoc destruction | `Style/EndlessMethod` | Data loss | Disabled; use `Tablecop/SafeEndlessMethod` |
341
+ | Rescue orphaning | `Style/EndlessMethod` | Scope/syntax errors | Disabled; use `Tablecop/SafeEndlessMethod` |
342
+ | module_eval failures | `Style/EndlessMethod` | Runtime NameError | Disabled; use `Tablecop/SafeEndlessMethod` |
343
+ | Modifier-if parse failures | `Style/EndlessMethod` | Parse-time NameError | Disabled; use `Tablecop/SafeEndlessMethod` |
344
+ | Heredoc alignment loop | `Layout/ExtraSpacing` | Process hangs | `ForceEqualSignAlignment: false` |
345
+ | `!!false` → `!false.nil?` | `Style/DoubleNegation` | Silent logic bug | Disabled |
346
+ | Mixed key types | `Style/HashExcept` | Data corruption | Disabled |
347
+
348
+ ## Example Output
349
+
350
+ Here's what code looks like with Tablecop's formatting:
351
+
352
+ ```ruby
353
+ # Method alignment
354
+ class Calculator
355
+ def add(x, y) = x + y
356
+ def subtract(x, y) = x - y
357
+ def multiply(x, y) = x * y
358
+ def divide(x, y) = x / y
359
+ end
360
+
361
+ # Assignment alignment
362
+ class Config
363
+ BASE_URL = "https://example.com"
364
+ API_VERSION = "v1"
365
+ TIMEOUT = 30
366
+
367
+ def initialize
368
+ @cache = {}
369
+ @enabled = true
370
+ @retries = 3
371
+ end
372
+ end
373
+
374
+ # Case statement condensing
375
+ def status_message(status)
376
+ case status
377
+ when :pending then "Waiting for approval"
378
+ when :approved then "Ready to proceed"
379
+ when :rejected then "Cannot continue"
380
+ when :completed then "All done"
381
+ end
382
+ end
383
+
384
+ # Hash alignment
385
+ config = {
386
+ host: "localhost",
387
+ port: 3000,
388
+ ssl: true,
389
+ timeout: 30,
390
+ retries: 3,
391
+ pool: 10
392
+ }
393
+ ```
394
+
395
+ ## Philosophy
396
+
397
+ Tablecop prioritizes:
398
+
399
+ 1. **Vertical alignment** - Code aligned in columns is easier to scan
400
+ 2. **Density** - Single-line expressions where appropriate reduce scrolling
401
+ 3. **Safety** - Avoid RuboCop's known autocorrect bugs
402
+ 4. **Readability** - Only condense when it improves clarity
403
+
404
+ This style works best for:
405
+ - Configuration files with many similar assignments
406
+ - Simple data transformations and mappings
407
+ - API clients with many similar methods
408
+ - DSLs with repetitive structures
409
+
410
+ This style may not work well for:
411
+ - Complex business logic with deep nesting
412
+ - Long method chains
413
+ - Files with highly variable line lengths
414
+
415
+ ## Development
416
+
417
+ After checking out the repo, run `bundle install` to install dependencies. Then:
418
+
419
+ ```bash
420
+ # Run RuboCop on the project
421
+ bundle exec rubocop
422
+
423
+ # Run autocorrect
424
+ bundle exec rubocop -a
425
+ ```
426
+
427
+ ## Contributing
428
+
429
+ Bug reports and pull requests are welcome on GitHub at https://github.com/v2-io/tablecop.
430
+
431
+ ## License
432
+
433
+ The gem is available as open source under the terms of the [MIT License](LICENSE).
@@ -0,0 +1,99 @@
1
+ # Tablecop Default Configuration
2
+ #
3
+ # Table-like, condensed Ruby formatting.
4
+ # These defaults prioritize vertical alignment and single-line expressions
5
+ # where they improve readability.
6
+ #
7
+ # CAUTION: Some alignment cops have known bugs with autocorrect.
8
+ # See docs/known-issues.md for details.
9
+
10
+ # =============================================================================
11
+ # Tablecop Custom Cops
12
+ # =============================================================================
13
+
14
+ Tablecop/AlignAssignments:
15
+ Description: "Align consecutive assignments on the = operator."
16
+ Enabled: true
17
+ VersionAdded: "0.1.0"
18
+
19
+ Tablecop/AlignMethods:
20
+ Description: "Align contiguous single-line method definitions."
21
+ Enabled: true
22
+ VersionAdded: "0.1.0"
23
+
24
+ Tablecop/CondenseWhen:
25
+ Description: "Condense multi-line `when` clauses to single lines when possible."
26
+ Enabled: true
27
+ VersionAdded: "0.1.0"
28
+
29
+ Tablecop/SafeEndlessMethod:
30
+ Description: "Convert multi-line methods to single-line, avoiding RuboCop bugs."
31
+ Enabled: true
32
+ VersionAdded: "0.1.0"
33
+
34
+ # =============================================================================
35
+ # Disabled Cops (conflict with Tablecop alignment)
36
+ # =============================================================================
37
+
38
+ # DISABLED: Conflicts with Tablecop/AlignAssignments.
39
+ # SpaceAroundOperators enforces exactly one space, which undoes alignment padding.
40
+ Layout/SpaceAroundOperators:
41
+ Enabled: false
42
+
43
+ # DISABLED: Conflicts with Tablecop/SafeEndlessMethod.
44
+ # These cops try to reformat blocks/methods that SafeEndlessMethod creates,
45
+ # causing infinite autocorrect loops.
46
+ Style/SingleLineDoEndBlock:
47
+ Enabled: false
48
+
49
+ Style/SingleLineMethods:
50
+ Enabled: false
51
+
52
+ # =============================================================================
53
+ # Disabled Cops (replaced by Tablecop equivalents)
54
+ # =============================================================================
55
+
56
+ # DISABLED: Style/EndlessMethod has critical autocorrect bugs:
57
+ # - Destroys heredoc content entirely
58
+ # - Breaks rescue clauses (orphans them outside the class)
59
+ # - Fails in module_eval contexts (Toys, DSLs)
60
+ # - Fails with modifier-if calling dynamic methods
61
+ # See docs/known-issues.md for details.
62
+ # Use Tablecop/SafeEndlessMethod instead.
63
+ Style/EndlessMethod:
64
+ Enabled: false
65
+
66
+ # Related to EndlessMethod - can cause issues in the same contexts
67
+ Style/AmbiguousEndlessMethodDefinition:
68
+ Enabled: false
69
+
70
+ # =============================================================================
71
+ # Layout - Alignment
72
+ # =============================================================================
73
+
74
+ # Require spaces inside hash braces: { foo: 1 }
75
+ Layout/SpaceInsideHashLiteralBraces:
76
+ EnforcedStyle: space
77
+
78
+ # Allow extra spacing for alignment
79
+ # NOTE: ForceEqualSignAlignment has a bug with compound operators (||=, &&=)
80
+ # that can create syntax errors. Use with caution.
81
+ Layout/ExtraSpacing:
82
+ AllowForAlignment: true
83
+ AllowBeforeTrailingComments: true
84
+ ForceEqualSignAlignment: false # Disabled due to ||= bug
85
+
86
+ # Table-style hash alignment
87
+ # NOTE: Can cause infinite loops when combined with other alignment cops.
88
+ # If you experience hangs, try switching to 'key' style.
89
+ Layout/HashAlignment:
90
+ EnforcedHashRocketStyle: table
91
+ EnforcedColonStyle: table
92
+ EnforcedLastArgumentHashStyle: always_inspect
93
+
94
+ # Consistent indentation (not aligned to brackets/parens)
95
+ Layout/FirstArrayElementIndentation:
96
+ EnforcedStyle: consistent
97
+
98
+ Layout/FirstArgumentIndentation:
99
+ EnforcedStyle: consistent