class RuboCop::Cop::Style::RegexpLiteral

This cop enforces using // or %r around regular expressions.

@example EnforcedStyle: slashes (default)

# bad
snake_case = %r{^[\dA-Z_]+$}

# bad
regex = %r{
  foo
  (bar)
  (baz)
}x

# good
snake_case = /^[\dA-Z_]+$/

# good
regex = /
  foo
  (bar)
  (baz)
/x

@example EnforcedStyle: percent_r

# bad
snake_case = /^[\dA-Z_]+$/

# bad
regex = /
  foo
  (bar)
  (baz)
/x

# good
snake_case = %r{^[\dA-Z_]+$}

# good
regex = %r{
  foo
  (bar)
  (baz)
}x

@example EnforcedStyle: mixed

# bad
snake_case = %r{^[\dA-Z_]+$}

# bad
regex = /
  foo
  (bar)
  (baz)
/x

# good
snake_case = /^[\dA-Z_]+$/

# good
regex = %r{
  foo
  (bar)
  (baz)
}x

@example AllowInnerSlashes: false (default)

# If `false`, the cop will always recommend using `%r` if one or more
# slashes are found in the regexp string.

# bad
x =~ /home\//

# good
x =~ %r{home/}

@example AllowInnerSlashes: true

# good
x =~ /home\//

Constants

MSG_USE_PERCENT_R
MSG_USE_SLASHES

Public Instance Methods

autocorrect(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 99
def autocorrect(node)
  lambda do |corrector|
    correct_delimiters(node, corrector)
    correct_inner_slashes(node, corrector)
  end
end
on_regexp(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 91
def on_regexp(node)
  if slash_literal?(node)
    check_slash_literal(node)
  else
    check_percent_r_literal(node)
  end
end

Private Instance Methods

allow_inner_slashes?() click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 149
def allow_inner_slashes?
  cop_config['AllowInnerSlashes']
end
allowed_mixed_percent_r?(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 136
def allowed_mixed_percent_r?(node)
  style == :mixed && node.multiline? ||
    contains_disallowed_slash?(node)
end
allowed_mixed_slash?(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 125
def allowed_mixed_slash?(node)
  style == :mixed && node.single_line? &&
    !contains_disallowed_slash?(node)
end
allowed_percent_r_literal?(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 130
def allowed_percent_r_literal?(node)
  style == :slashes && contains_disallowed_slash?(node) ||
    style == :percent_r ||
    allowed_mixed_percent_r?(node)
end
allowed_slash_literal?(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 120
def allowed_slash_literal?(node)
  style == :slashes && !contains_disallowed_slash?(node) ||
    allowed_mixed_slash?(node)
end
calculate_replacement(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 218
def calculate_replacement(node)
  if slash_literal?(node)
    ['%r', ''].zip(preferred_delimiters).map(&:join)
  else
    %w[/ /]
  end
end
check_percent_r_literal(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 114
def check_percent_r_literal(node)
  return if allowed_percent_r_literal?(node)

  add_offense(node, message: MSG_USE_SLASHES)
end
check_slash_literal(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 108
def check_slash_literal(node)
  return if allowed_slash_literal?(node)

  add_offense(node, message: MSG_USE_PERCENT_R)
end
contains_disallowed_slash?(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 141
def contains_disallowed_slash?(node)
  !allow_inner_slashes? && contains_slash?(node)
end
contains_slash?(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 145
def contains_slash?(node)
  node_body(node).include?('/')
end
correct_delimiters(node, corrector) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 167
def correct_delimiters(node, corrector)
  replacement = calculate_replacement(node)
  corrector.replace(node.loc.begin, replacement.first)
  corrector.replace(node.loc.end, replacement.last)
end
correct_inner_slashes(node, corrector) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 173
def correct_inner_slashes(node, corrector)
  regexp_begin = node.loc.begin.end_pos

  inner_slash_indices(node).each do |index|
    start = regexp_begin + index

    corrector.replace(
      range_between(
        start,
        start + inner_slash_before_correction(node).length
      ),
      inner_slash_after_correction(node)
    )
  end
end
inner_slash_after_correction(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 206
def inner_slash_after_correction(node)
  inner_slash_for(calculate_replacement(node).first)
end
inner_slash_before_correction(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 202
def inner_slash_before_correction(node)
  inner_slash_for(node.loc.begin.source)
end
inner_slash_for(opening_delimiter) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 210
def inner_slash_for(opening_delimiter)
  if ['/', '%r/'].include?(opening_delimiter)
    '\/'
  else
    '/'
  end
end
inner_slash_indices(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 189
def inner_slash_indices(node)
  text    = node_body(node, include_begin_nodes: true)
  pattern = inner_slash_before_correction(node)
  index   = -1
  indices = []

  while (index = text.index(pattern, index + 1))
    indices << index
  end

  indices
end
node_body(node, include_begin_nodes: false) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 153
def node_body(node, include_begin_nodes: false)
  types = include_begin_nodes ? %i[str begin] : %i[str]
  node.each_child_node(*types).map(&:source).join
end
preferred_delimiters() click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 162
def preferred_delimiters
  config.for_cop('Style/PercentLiteralDelimiters') \
    ['PreferredDelimiters']['%r'].split(//)
end
slash_literal?(node) click to toggle source
# File lib/rubocop/cop/style/regexp_literal.rb, line 158
def slash_literal?(node)
  node.loc.begin.source == '/'
end