Skip to content

Commit

Permalink
Add Style/RedundantRegexpCharacterClass cop
Browse files Browse the repository at this point in the history
```ruby
  # This cop checks for unnecessary single-element Regexp character classes.
  #
  # @example
  #
  #   # bad
  #   r = /[x]/
  #
  #   # good
  #   r = /x/
  #
  #   # bad
  #   r = /[\s]/
  #
  #   # good
  #   r = /\s/
  #
  #   # good
  #   r = /[ab]/
```
  • Loading branch information
owst committed Jun 1, 2020
1 parent de45975 commit 9cddd6b
Show file tree
Hide file tree
Showing 11 changed files with 344 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@
### New features

* [#6289](https://github.com/rubocop-hq/rubocop/issues/6289): Add new `CheckDefinitionPathHierarchy` option for `Naming/FileName`. ([@jschneid][])
* [#8055](https://github.com/rubocop-hq/rubocop/pull/8055): Add new `Style/RedundantRegexpCharacterClass` cop. ([@owst][])
* [#8069](https://github.com/rubocop-hq/rubocop/issues/8069): New option for `expect_offense` to help format offense templates. ([@marcandre][])
* [#7908](https://github.com/rubocop-hq/rubocop/pull/7908): Add new `Style/RedundantRegexpEscape` cop. ([@owst][])
* [#7978](https://github.com/rubocop-hq/rubocop/pull/7978): Add new option `OnlyFor` to the `Bundler/GemComment` cop. ([@ric2b][])
Expand Down
5 changes: 5 additions & 0 deletions config/default.yml
Expand Up @@ -3613,6 +3613,11 @@ Style/RedundantPercentQ:
Enabled: true
VersionAdded: '0.76'

Style/RedundantRegexpCharacterClass:
Description: 'Checks for unnecessary single-element Regexp character classes.'
Enabled: pending
VersionAdded: '0.85'

Style/RedundantRegexpEscape:
Description: 'Checks for redundant escapes in Regexps.'
Enabled: pending
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Expand Up @@ -437,6 +437,7 @@ In the following section you find all available cops:
* xref:cops_style.adoc#styleredundantinterpolation[Style/RedundantInterpolation]
* xref:cops_style.adoc#styleredundantparentheses[Style/RedundantParentheses]
* xref:cops_style.adoc#styleredundantpercentq[Style/RedundantPercentQ]
* xref:cops_style.adoc#styleredundantregexpcharacterclass[Style/RedundantRegexpCharacterClass]
* xref:cops_style.adoc#styleredundantregexpescape[Style/RedundantRegexpEscape]
* xref:cops_style.adoc#styleredundantreturn[Style/RedundantReturn]
* xref:cops_style.adoc#styleredundantself[Style/RedundantSelf]
Expand Down
34 changes: 34 additions & 0 deletions docs/modules/ROOT/pages/cops_style.adoc
Expand Up @@ -7116,6 +7116,40 @@ question = '"What did you say?"'

* https://rubystyle.guide#percent-q

== Style/RedundantRegexpCharacterClass

|===
| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged

| Pending
| Yes
| Yes
| 0.85
| -
|===

This cop checks for unnecessary single-element Regexp character classes.

=== Examples

[source,ruby]
----
# bad
r = /[x]/
# good
r = /x/
# bad
r = /[\s]/
# good
r = /\s/
# good
r = /[ab]/
----

== Style/RedundantRegexpEscape

|===
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Expand Up @@ -476,6 +476,7 @@
require_relative 'rubocop/cop/style/redundant_interpolation'
require_relative 'rubocop/cop/style/redundant_parentheses'
require_relative 'rubocop/cop/style/redundant_percent_q'
require_relative 'rubocop/cop/style/redundant_regexp_character_class'
require_relative 'rubocop/cop/style/redundant_regexp_escape'
require_relative 'rubocop/cop/style/redundant_return'
require_relative 'rubocop/cop/style/redundant_self'
Expand Down
Expand Up @@ -190,7 +190,7 @@ def incorrect_parenthesis_removal_begin(node)
def safe_to_remove_line_containing_closing_paren?(node)
last_line = processed_source[node.loc.end.line - 1]
# Safe to remove if last line only contains `)`, `,`, and whitespace.
last_line.match?(/^[ ]*\)[ ]{0,20},{0,1}[ ]*$/)
last_line.match?(/^ *\) {0,20},{0,1} *$/)
end

def incorrect_parenthesis_removal_end(node)
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/migration/department_name.rb
Expand Up @@ -24,7 +24,7 @@ def investigate(processed_source)

offset = Regexp.last_match(1).length

Regexp.last_match(4).scan(/[^,]+|[\W]+/) do |name|
Regexp.last_match(4).scan(/[^,]+|\W+/) do |name|
trimmed_name = name.strip

unless valid_content_token?(trimmed_name)
Expand Down
2 changes: 1 addition & 1 deletion lib/rubocop/cop/mixin/uncommunicative_name.rb
Expand Up @@ -18,7 +18,7 @@ def check(node, args)
full_name = arg.children.first.to_s
next if full_name == '_'

name = full_name.gsub(/\A([_]+)/, '')
name = full_name.gsub(/\A(_+)/, '')
next if (arg.restarg_type? || arg.kwrestarg_type?) && name.empty?
next if allowed_names.include?(name)

Expand Down
89 changes: 89 additions & 0 deletions lib/rubocop/cop/style/redundant_regexp_character_class.rb
@@ -0,0 +1,89 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# This cop checks for unnecessary single-element Regexp character classes.
#
# @example
#
# # bad
# r = /[x]/
#
# # good
# r = /x/
#
# # bad
# r = /[\s]/
#
# # good
# r = /\s/
#
# # good
# r = /[ab]/
class RedundantRegexpCharacterClass < Cop
include MatchRange
include RegexpLiteralHelp

MSG_REDUNDANT_CHARACTER_CLASS = 'Redundant single-element character class, ' \
'`%<char_class>s` can be replaced with `%<element>s`.'

PATTERN = /
(
(?<!\\) # No \-prefix (i.e. not escaped)
\[ # Literal [
(?!\#\{) # Not (the start of) an interpolation
(?: # Either...
\\. | # Any escaped character
[^.*+?{}()|$] | # or one that doesn't require escaping outside the character class
\\[upP]\{[^}]+\} # or a unicode code-point or property
)
\] # Literal ]
)
/x.freeze

def on_regexp(node)
each_redundant_character_class(node) do |loc|
next if whitespace_in_free_space_mode?(node, loc)

add_offense(
node,
location: loc,
message: format(
MSG_REDUNDANT_CHARACTER_CLASS,
char_class: loc.source,
element: without_character_class(loc)
)
)
end
end

def autocorrect(node)
lambda do |corrector|
each_redundant_character_class(node) do |loc|
corrector.replace(loc, without_character_class(loc))
end
end
end

def each_redundant_character_class(node)
each_match_range(node.loc.expression, PATTERN) do |loc|
yield loc
end
end

private

def without_character_class(loc)
loc.source[1..-2]
end

def whitespace_in_free_space_mode?(node, loc)
return false unless freespace_mode_regexp?(node)

/\[\s\]/.match?(loc.source)
end
end
end
end
end

0 comments on commit 9cddd6b

Please sign in to comment.