Skip to content

Commit

Permalink
Add support for tabs in indentation (#7867)
Browse files Browse the repository at this point in the history
This commit adds SupportedStyles to Layout/Tab - spaces and tabs. It also renames the cop to
to Layout/IndentationStyle, as the old name no longer reflects its purpose.

One notable difference is that before the `Layout/Tab` cop was warning on tabs anywhere in the source, but now we're focusing only on tabs used for indentation purposes.
  • Loading branch information
DracoAter committed Apr 15, 2020
1 parent 85ecd05 commit 6b9510d
Show file tree
Hide file tree
Showing 25 changed files with 446 additions and 248 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@

### New features

* [#7867](https://github.com/rubocop-hq/rubocop/pull/7867): Add support for tabs in indentation. ([@DracoAter][])
* [#7863](https://github.com/rubocop-hq/rubocop/issues/7863): Corrector now accepts nodes in addition to ranges. ([@marcandre][])
* [#7862](https://github.com/rubocop-hq/rubocop/issues/7862): Corrector now has a `wrap` method. ([@marcandre][])
* [#7850](https://github.com/rubocop-hq/rubocop/issues/7850): Make it possible to enable/disable pending cops. ([@koic][])
Expand All @@ -12,6 +13,10 @@
* [#7384](https://github.com/rubocop-hq/rubocop/pull/7384): Add new `Style/DisableCopsWithinSourceCodeDirective` cop. ([@egze][])
* [#7826](https://github.com/rubocop-hq/rubocop/issues/7826): Add new `Layout/SpaceAroundMethodCallOperator` cop. ([@saurabhmaurya15][])

### Changes

* **(Breaking)** Renamed `Layout/Tab` cop to `Layout/IndentationStyle`. ([@DracoAter][])

### Bug fixes

* [#7871](https://github.com/rubocop-hq/rubocop/pull/7871): Fix an auto-correction bug in `Lint/BooleanSymbol`. ([@knu][])
Expand Down Expand Up @@ -4452,3 +4457,4 @@
[@rafaelfranca]: https://github.com/rafaelfranca
[@knu]: https://github.com/knu
[@saurabhmaurya15]: https://github.com/saurabhmaurya15
[@DracoAter]: https:/github.com/DracoAter
28 changes: 16 additions & 12 deletions config/default.yml
Expand Up @@ -801,6 +801,22 @@ Layout/IndentationConsistency:
# A reference to `EnforcedStyle: indented_internal_methods`.
- https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#follow-the-coding-conventions

Layout/IndentationStyle:
Description: 'Consistent indentation either with tabs only or spaces only.'
StyleGuide: '#spaces-indentation'
Enabled: true
VersionAdded: '0.49'
VersionChanged: '0.82'
# By default, the indentation width from Layout/IndentationWidth is used
# But it can be overridden by setting this parameter
# It is used during auto-correction to determine how many spaces should
# replace each tab.
IndentationWidth: ~
EnforcedStyle: spaces
SupportedStyles:
- spaces
- tabs

Layout/IndentationWidth:
Description: 'Use 2 spaces for indentation.'
StyleGuide: '#spaces-indentation'
Expand Down Expand Up @@ -1263,18 +1279,6 @@ Layout/SpaceInsideStringInterpolation:
- space
- no_space

Layout/Tab:
Description: 'No hard tabs.'
StyleGuide: '#spaces-indentation'
Enabled: true
VersionAdded: '0.49'
VersionChanged: '0.51'
# By default, the indentation width from Layout/IndentationWidth is used
# But it can be overridden by setting this parameter
# It is used during auto-correction to determine how many spaces should
# replace each tab.
IndentationWidth: ~

Layout/TrailingEmptyLines:
Description: 'Checks trailing blank lines and final newline.'
StyleGuide: '#newline-eof'
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop.rb
Expand Up @@ -235,6 +235,7 @@
require_relative 'rubocop/cop/layout/heredoc_argument_closing_parenthesis'
require_relative 'rubocop/cop/layout/heredoc_indentation'
require_relative 'rubocop/cop/layout/indentation_consistency'
require_relative 'rubocop/cop/layout/indentation_style'
require_relative 'rubocop/cop/layout/indentation_width'
require_relative 'rubocop/cop/layout/initial_indentation'
require_relative 'rubocop/cop/layout/leading_comment_space'
Expand Down Expand Up @@ -278,7 +279,6 @@
require_relative 'rubocop/cop/layout/space_inside_range_literal'
require_relative 'rubocop/cop/layout/space_inside_reference_brackets'
require_relative 'rubocop/cop/layout/space_inside_string_interpolation'
require_relative 'rubocop/cop/layout/tab'
require_relative 'rubocop/cop/layout/trailing_empty_lines'
require_relative 'rubocop/cop/layout/trailing_whitespace'

Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/config_obsoletion.rb
Expand Up @@ -17,6 +17,7 @@ class ConfigObsoletion
'Layout/IndentHash' => 'Layout/FirstHashElementIndentation',
'Layout/IndentHeredoc' => 'Layout/HeredocIndentation',
'Layout/LeadingBlankLines' => 'Layout/LeadingEmptyLines',
'Layout/Tab' => 'Layout/IndentationStyle',
'Layout/TrailingBlankLines' => 'Layout/TrailingEmptyLines',
'Lint/DuplicatedKey' => 'Lint/DuplicateHashKey',
'Lint/EndInMethod' => 'Style/EndBlock',
Expand Down
10 changes: 5 additions & 5 deletions lib/rubocop/cop/badge.rb
Expand Up @@ -4,11 +4,11 @@ module RuboCop
module Cop
# Identifier of all cops containing a department and cop name.
#
# All cops are identified by their badge. For example, the badge
# for `RuboCop::Cop::Layout::Tab` is `Layout/Tab`. Badges can be
# parsed as either `Department/CopName` or just `CopName` to allow
# for badge references in source files that omit the department
# for RuboCop to infer.
# All cops are identified by their badge. For example, the badge for
# `RuboCop::Cop::Layout::IndentationStyle` is `Layout/IndentationStyle`.
# Badges can be parsed as either `Department/CopName` or just `CopName` to
# allow for badge references in source files that omit the department for
# RuboCop to infer.
class Badge
# Error raised when a badge parse fails.
class InvalidBadge < Error
Expand Down
Expand Up @@ -5,9 +5,10 @@
module RuboCop
module Cop
module Layout
# This cop checks for tabs inside the source code.
# This cop checks that the indentation method is consistent.
# Either tabs only or spaces only are used for indentation.
#
# @example
# @example EnforcedStyle: spaces (default)
# # bad
# # This example uses a tab to indent bar.
# def foo
Expand All @@ -20,17 +21,30 @@ module Layout
# bar
# end
#
class Tab < Cop
# @example EnforcedStyle: tabs
# # bad
# # This example uses spaces to indent bar.
# def foo
# bar
# end
#
# # good
# # This example uses a tab to indent bar.
# def foo
# bar
# end
class IndentationStyle < Cop
include Alignment
include ConfigurableEnforcedStyle
include RangeHelp

MSG = 'Tab detected.'
MSG = '%<type>s detected in indentation.'

def investigate(processed_source)
str_ranges = string_literal_ranges(processed_source.ast)

processed_source.lines.each.with_index(1) do |line, lineno|
match = line.match(/\t+/)
match = find_offence(line)
next unless match

range = source_range(processed_source.buffer,
Expand All @@ -43,13 +57,37 @@ def investigate(processed_source)
end

def autocorrect(range)
if range.source.include?("\t")
autocorrect_lambda_for_tabs(range)
else
autocorrect_lambda_for_spaces(range)
end
end

private

def find_offence(line)
if style == :spaces
line.match(/\A\s*\t+/)
else
line.match(/\A\s* +/)
end
end

def autocorrect_lambda_for_tabs(range)
lambda do |corrector|
spaces = ' ' * configured_indentation_width
corrector.replace(range, range.source.gsub(/\t/, spaces))
end
end

private
def autocorrect_lambda_for_spaces(range)
lambda do |corrector|
corrector.replace(range, range.source.gsub(/\A\s+/) do |match|
"\t" * (match.size / configured_indentation_width)
end)
end
end

def in_string_literal?(ranges, tabs_range)
ranges.any? { |range| range.contains?(tabs_range) }
Expand All @@ -69,6 +107,10 @@ def string_literal_ranges(ast)
end
end
end

def message(_node)
format(MSG, type: style == :spaces ? 'Tab' : 'Space')
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/layout/line_length.rb
Expand Up @@ -8,7 +8,7 @@ module Layout
# This cop checks the length of lines in the source code.
# The maximum length is configurable.
# The tab size is configured in the `IndentationWidth`
# of the `Layout/Tab` cop.
# of the `Layout/IndentationStyle` cop.
# It also ignores a shebang line by default.
#
# This cop has some autocorrection capabilities.
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/mixin/line_length_help.rb
Expand Up @@ -63,7 +63,7 @@ def indentation_difference(line)
end

def tab_indentation_width
config.for_cop('Layout/Tab')['IndentationWidth']
config.for_cop('Layout/IndentationStyle')['IndentationWidth']
end

def uri_regexp
Expand Down
7 changes: 4 additions & 3 deletions lib/rubocop/cop/mixin/statement_modifier.rb
Expand Up @@ -58,12 +58,13 @@ def max_line_length
end

def indentation_multiplier
return 1 if config.for_cop('Layout/Tab')['Enabled']
return 1 if config.for_cop('Layout/IndentationStyle')['Enabled']

default_configuration = RuboCop::ConfigLoader.default_configuration
config.for_cop('Layout/Tab')['IndentationWidth'] ||
config.for_cop('Layout/IndentationStyle')['IndentationWidth'] ||
config.for_cop('Layout/IndentationWidth')['Width'] ||
default_configuration.for_cop('Layout/Tab')['IndentationWidth'] ||
default_configuration
.for_cop('Layout/IndentationStyle')['IndentationWidth'] ||
default_configuration.for_cop('Layout/IndentationWidth')['Width']
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/style/if_unless_modifier.rb
Expand Up @@ -9,7 +9,7 @@ module Style
#
# The maximum line length is configured in the `Layout/LineLength`
# cop. The tab size is configured in the `IndentationWidth` of the
# `Layout/Tab` cop.
# `Layout/IndentationStyle` cop.
#
# @example
# # bad
Expand Down
2 changes: 1 addition & 1 deletion manual/cops.md
Expand Up @@ -128,6 +128,7 @@ In the following section you find all available cops:
* [Layout/HeredocArgumentClosingParenthesis](cops_layout.md#layoutheredocargumentclosingparenthesis)
* [Layout/HeredocIndentation](cops_layout.md#layoutheredocindentation)
* [Layout/IndentationConsistency](cops_layout.md#layoutindentationconsistency)
* [Layout/IndentationStyle](cops_layout.md#layoutindentationstyle)
* [Layout/IndentationWidth](cops_layout.md#layoutindentationwidth)
* [Layout/InitialIndentation](cops_layout.md#layoutinitialindentation)
* [Layout/LeadingCommentSpace](cops_layout.md#layoutleadingcommentspace)
Expand Down Expand Up @@ -171,7 +172,6 @@ In the following section you find all available cops:
* [Layout/SpaceInsideRangeLiteral](cops_layout.md#layoutspaceinsiderangeliteral)
* [Layout/SpaceInsideReferenceBrackets](cops_layout.md#layoutspaceinsidereferencebrackets)
* [Layout/SpaceInsideStringInterpolation](cops_layout.md#layoutspaceinsidestringinterpolation)
* [Layout/Tab](cops_layout.md#layouttab)
* [Layout/TrailingEmptyLines](cops_layout.md#layouttrailingemptylines)
* [Layout/TrailingWhitespace](cops_layout.md#layouttrailingwhitespace)

Expand Down
89 changes: 54 additions & 35 deletions manual/cops_layout.md
Expand Up @@ -2720,6 +2720,59 @@ EnforcedStyle | `normal` | `normal`, `indented_internal_methods`
* [https://rubystyle.guide#spaces-indentation](https://rubystyle.guide#spaces-indentation)
* [https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#follow-the-coding-conventions](https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#follow-the-coding-conventions)

## Layout/IndentationStyle

Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
--- | --- | --- | --- | ---
Enabled | Yes | Yes | 0.49 | 0.82

This cop checks that the indentation method is consistent.
Either tabs only or spaces only are used for indentation.

### Examples

#### EnforcedStyle: spaces (default)

```ruby
# bad
# This example uses a tab to indent bar.
def foo
bar
end

# good
# This example uses spaces to indent bar.
def foo
bar
end
```
#### EnforcedStyle: tabs

```ruby
# bad
# This example uses spaces to indent bar.
def foo
bar
end

# good
# This example uses a tab to indent bar.
def foo
bar
end
```

### Configurable attributes

Name | Default value | Configurable values
--- | --- | ---
IndentationWidth | `<none>` | Integer
EnforcedStyle | `spaces` | `spaces`, `tabs`

### References

* [https://rubystyle.guide#spaces-indentation](https://rubystyle.guide#spaces-indentation)

## Layout/IndentationWidth

Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
Expand Down Expand Up @@ -2916,7 +2969,7 @@ Enabled | Yes | Yes | 0.25 | 0.78
This cop checks the length of lines in the source code.
The maximum length is configurable.
The tab size is configured in the `IndentationWidth`
of the `Layout/Tab` cop.
of the `Layout/IndentationStyle` cop.
It also ignores a shebang line by default.

This cop has some autocorrection capabilities.
Expand Down Expand Up @@ -4873,40 +4926,6 @@ EnforcedStyle | `no_space` | `space`, `no_space`

* [https://rubystyle.guide#string-interpolation](https://rubystyle.guide#string-interpolation)

## Layout/Tab

Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
--- | --- | --- | --- | ---
Enabled | Yes | Yes | 0.49 | 0.51

This cop checks for tabs inside the source code.

### Examples

```ruby
# bad
# This example uses a tab to indent bar.
def foo
bar
end

# good
# This example uses spaces to indent bar.
def foo
bar
end
```

### Configurable attributes

Name | Default value | Configurable values
--- | --- | ---
IndentationWidth | `<none>` | Integer

### References

* [https://rubystyle.guide#spaces-indentation](https://rubystyle.guide#spaces-indentation)

## Layout/TrailingEmptyLines

Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
Expand Down
2 changes: 1 addition & 1 deletion manual/cops_style.md
Expand Up @@ -2925,7 +2925,7 @@ written as modifier `if`/`unless`. The cop also checks for modifier

The maximum line length is configured in the `Layout/LineLength`
cop. The tab size is configured in the `IndentationWidth` of the
`Layout/Tab` cop.
`Layout/IndentationStyle` cop.

### Examples

Expand Down

0 comments on commit 6b9510d

Please sign in to comment.