rubocop 1.52.1 → 1.53.1
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/README.md +1 -1
- data/config/default.yml +38 -1
- data/lib/rubocop/cli/command/lsp.rb +19 -0
- data/lib/rubocop/cli.rb +3 -0
- data/lib/rubocop/config_loader_resolver.rb +4 -3
- data/lib/rubocop/cop/bundler/gem_comment.rb +1 -1
- data/lib/rubocop/cop/bundler/gem_version.rb +2 -2
- data/lib/rubocop/cop/gemspec/dependency_version.rb +2 -2
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +3 -3
- data/lib/rubocop/cop/layout/class_structure.rb +7 -0
- data/lib/rubocop/cop/layout/closing_heredoc_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/empty_line_between_defs.rb +1 -1
- data/lib/rubocop/cop/layout/empty_lines_around_exception_handling_keywords.rb +2 -0
- data/lib/rubocop/cop/layout/indentation_style.rb +1 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +2 -2
- data/lib/rubocop/cop/layout/redundant_line_break.rb +1 -1
- data/lib/rubocop/cop/layout/space_inside_range_literal.rb +1 -1
- data/lib/rubocop/cop/lint/debugger.rb +1 -1
- data/lib/rubocop/cop/lint/duplicate_hash_key.rb +2 -1
- data/lib/rubocop/cop/lint/duplicate_regexp_character_class_element.rb +46 -19
- data/lib/rubocop/cop/lint/heredoc_method_call_position.rb +1 -1
- data/lib/rubocop/cop/lint/missing_super.rb +31 -5
- data/lib/rubocop/cop/lint/mixed_case_range.rb +109 -0
- data/lib/rubocop/cop/lint/number_conversion.rb +5 -0
- data/lib/rubocop/cop/lint/redundant_regexp_quantifiers.rb +120 -0
- data/lib/rubocop/cop/lint/redundant_require_statement.rb +8 -3
- data/lib/rubocop/cop/lint/suppressed_exception.rb +1 -1
- data/lib/rubocop/cop/lint/symbol_conversion.rb +1 -1
- data/lib/rubocop/cop/lint/void.rb +1 -1
- data/lib/rubocop/cop/migration/department_name.rb +2 -2
- data/lib/rubocop/cop/mixin/comments_help.rb +1 -1
- data/lib/rubocop/cop/mixin/end_keyword_alignment.rb +1 -1
- data/lib/rubocop/cop/mixin/percent_literal.rb +1 -1
- data/lib/rubocop/cop/naming/block_forwarding.rb +1 -1
- data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +3 -3
- data/lib/rubocop/cop/style/block_comments.rb +1 -1
- data/lib/rubocop/cop/style/block_delimiters.rb +3 -3
- data/lib/rubocop/cop/style/conditional_assignment.rb +3 -1
- data/lib/rubocop/cop/style/document_dynamic_eval_definition.rb +1 -1
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +6 -2
- data/lib/rubocop/cop/style/invertible_unless_condition.rb +1 -1
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -2
- data/lib/rubocop/cop/style/preferred_hash_methods.rb +1 -1
- data/lib/rubocop/cop/style/redundant_begin.rb +1 -1
- data/lib/rubocop/cop/style/redundant_conditional.rb +1 -1
- data/lib/rubocop/cop/style/redundant_current_directory_in_path.rb +38 -0
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +2 -2
- data/lib/rubocop/cop/style/redundant_parentheses.rb +1 -1
- data/lib/rubocop/cop/style/redundant_regexp_argument.rb +97 -0
- data/lib/rubocop/cop/style/redundant_regexp_escape.rb +2 -1
- data/lib/rubocop/cop/style/redundant_self_assignment_branch.rb +3 -1
- data/lib/rubocop/cop/style/redundant_sort.rb +1 -1
- data/lib/rubocop/cop/style/redundant_string_escape.rb +2 -0
- data/lib/rubocop/cop/style/return_nil_in_predicate_method_definition.rb +81 -0
- data/lib/rubocop/cop/style/signal_exception.rb +1 -1
- data/lib/rubocop/cop/style/yaml_file_read.rb +66 -0
- data/lib/rubocop/cop/util.rb +1 -1
- data/lib/rubocop/cop/utils/regexp_ranges.rb +100 -0
- data/lib/rubocop/cops_documentation_generator.rb +1 -1
- data/lib/rubocop/ext/regexp_parser.rb +4 -1
- data/lib/rubocop/lsp/logger.rb +22 -0
- data/lib/rubocop/lsp/routes.rb +223 -0
- data/lib/rubocop/lsp/runtime.rb +79 -0
- data/lib/rubocop/lsp/server.rb +62 -0
- data/lib/rubocop/lsp/severity.rb +27 -0
- data/lib/rubocop/options.rb +11 -1
- data/lib/rubocop/version.rb +1 -1
- data/lib/rubocop.rb +8 -0
- metadata +30 -3
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Lint
|
6
|
+
# Checks for redundant quantifiers inside Regexp literals.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# /(?:x+)+/
|
11
|
+
#
|
12
|
+
# # good
|
13
|
+
# /(?:x)+/
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# /(?:x+)/
|
17
|
+
#
|
18
|
+
# # bad
|
19
|
+
# /(?:x+)?/
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# /(?:x)*/
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# /(?:x*)/
|
26
|
+
class RedundantRegexpQuantifiers < Base
|
27
|
+
include RangeHelp
|
28
|
+
extend AutoCorrector
|
29
|
+
|
30
|
+
MSG_REDUNDANT_QUANTIFIER = 'Replace redundant quantifiers ' \
|
31
|
+
'`%<inner_quantifier>s` and `%<outer_quantifier>s` ' \
|
32
|
+
'with a single `%<replacement>s`.'
|
33
|
+
|
34
|
+
def on_regexp(node)
|
35
|
+
each_redundantly_quantified_pair(node) do |group, child|
|
36
|
+
replacement = merged_quantifier(group, child)
|
37
|
+
add_offense(
|
38
|
+
quantifier_range(group, child),
|
39
|
+
message: message(group, child, replacement)
|
40
|
+
) do |corrector|
|
41
|
+
# drop outer quantifier
|
42
|
+
corrector.replace(group.loc.quantifier, '')
|
43
|
+
# replace inner quantifier
|
44
|
+
corrector.replace(child.loc.quantifier, replacement)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def each_redundantly_quantified_pair(node)
|
52
|
+
seen = Set.new
|
53
|
+
node.parsed_tree&.each_expression do |(expr)|
|
54
|
+
next if seen.include?(expr) || !redundant_group?(expr) || !mergeable_quantifier(expr)
|
55
|
+
|
56
|
+
expr.each_expression do |(subexp)|
|
57
|
+
seen << subexp
|
58
|
+
break unless redundantly_quantifiable?(subexp)
|
59
|
+
|
60
|
+
yield(expr, subexp) if mergeable_quantifier(subexp)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def redundant_group?(expr)
|
66
|
+
expr.is?(:passive, :group) && expr.count { |child| child.type != :free_space } == 1
|
67
|
+
end
|
68
|
+
|
69
|
+
def redundantly_quantifiable?(node)
|
70
|
+
redundant_group?(node) || character_set?(node) || node.terminal?
|
71
|
+
end
|
72
|
+
|
73
|
+
def character_set?(expr)
|
74
|
+
expr.is?(:character, :set)
|
75
|
+
end
|
76
|
+
|
77
|
+
def mergeable_quantifier(expr)
|
78
|
+
# Merging reluctant or possessive quantifiers would be more complex,
|
79
|
+
# and Ruby does not emit warnings for these cases.
|
80
|
+
return unless expr.quantifier&.greedy?
|
81
|
+
|
82
|
+
# normalize quantifiers, e.g. "{1,}" => "+"
|
83
|
+
case expr.quantity
|
84
|
+
when [0, -1]
|
85
|
+
'*'
|
86
|
+
when [0, 1]
|
87
|
+
'?'
|
88
|
+
when [1, -1]
|
89
|
+
'+'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def merged_quantifier(exp1, exp2)
|
94
|
+
quantifier1 = mergeable_quantifier(exp1)
|
95
|
+
quantifier2 = mergeable_quantifier(exp2)
|
96
|
+
if quantifier1 == quantifier2
|
97
|
+
# (?:a+)+ equals (?:a+) ; (?:a*)* equals (?:a*) ; # (?:a?)? equals (?:a?)
|
98
|
+
quantifier1
|
99
|
+
else
|
100
|
+
# (?:a+)*, (?:a+)?, (?:a*)+, (?:a*)?, (?:a?)+, (?:a?)* - all equal (?:a*)
|
101
|
+
'*'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def quantifier_range(group, child)
|
106
|
+
range_between(child.loc.quantifier.begin_pos, group.loc.quantifier.end_pos)
|
107
|
+
end
|
108
|
+
|
109
|
+
def message(group, child, replacement)
|
110
|
+
format(
|
111
|
+
MSG_REDUNDANT_QUANTIFIER,
|
112
|
+
inner_quantifier: child.quantifier.to_s,
|
113
|
+
outer_quantifier: group.quantifier.to_s,
|
114
|
+
replacement: replacement
|
115
|
+
)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -49,6 +49,11 @@ module RuboCop
|
|
49
49
|
(str #redundant_feature?))
|
50
50
|
PATTERN
|
51
51
|
|
52
|
+
# @!method pp_const?(node)
|
53
|
+
def_node_matcher :pp_const?, <<~PATTERN
|
54
|
+
(const {nil? cbase} :PP)
|
55
|
+
PATTERN
|
56
|
+
|
52
57
|
def on_send(node)
|
53
58
|
return unless redundant_require_statement?(node)
|
54
59
|
|
@@ -72,16 +77,16 @@ module RuboCop
|
|
72
77
|
feature_name == 'enumerator' ||
|
73
78
|
(target_ruby_version >= 2.1 && feature_name == 'thread') ||
|
74
79
|
(target_ruby_version >= 2.2 && RUBY_22_LOADED_FEATURES.include?(feature_name)) ||
|
75
|
-
(target_ruby_version >= 2.5 && feature_name == 'pp' && !
|
80
|
+
(target_ruby_version >= 2.5 && feature_name == 'pp' && !need_to_require_pp?) ||
|
76
81
|
(target_ruby_version >= 2.7 && feature_name == 'ruby2_keywords') ||
|
77
82
|
(target_ruby_version >= 3.1 && feature_name == 'fiber') ||
|
78
83
|
(target_ruby_version >= 3.2 && feature_name == 'set')
|
79
84
|
end
|
80
85
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
81
86
|
|
82
|
-
def
|
87
|
+
def need_to_require_pp?
|
83
88
|
processed_source.ast.each_descendant(:send).any? do |node|
|
84
|
-
PRETTY_PRINT_METHODS.include?(node.method_name)
|
89
|
+
pp_const?(node.receiver) || PRETTY_PRINT_METHODS.include?(node.method_name)
|
85
90
|
end
|
86
91
|
end
|
87
92
|
end
|
@@ -117,7 +117,7 @@ module RuboCop
|
|
117
117
|
|
118
118
|
def comment_between_rescue_and_end?(node)
|
119
119
|
ancestor = node.each_ancestor(:kwbegin, :def, :defs, :block, :numblock).first
|
120
|
-
return unless ancestor
|
120
|
+
return false unless ancestor
|
121
121
|
|
122
122
|
end_line = ancestor.loc.end.line
|
123
123
|
processed_source[node.first_line...end_line].any? { |line| comment_line?(line) }
|
@@ -124,7 +124,7 @@ module RuboCop
|
|
124
124
|
source == value ||
|
125
125
|
# `Symbol#inspect` uses double quotes, but allow single-quoted
|
126
126
|
# symbols to work as well.
|
127
|
-
source.tr("'", '"') == value
|
127
|
+
source.gsub('"', '\"').tr("'", '"') == value
|
128
128
|
end
|
129
129
|
|
130
130
|
def requires_quotes?(sym_node)
|
@@ -144,7 +144,7 @@ module RuboCop
|
|
144
144
|
end
|
145
145
|
|
146
146
|
def check_nonmutating(node)
|
147
|
-
return
|
147
|
+
return if !node.send_type? && !node.block_type? && !node.numblock_type?
|
148
148
|
|
149
149
|
method_name = node.method_name
|
150
150
|
return unless NONMUTATING_METHODS.include?(method_name)
|
@@ -16,7 +16,7 @@ module RuboCop
|
|
16
16
|
# The token that makes up a disable comment.
|
17
17
|
# The allowed specification for comments after `# rubocop: disable` is
|
18
18
|
# `DepartmentName/CopName` or` all`.
|
19
|
-
DISABLING_COPS_CONTENT_TOKEN = %r{[A-z]+/[A-z]+|all}.freeze
|
19
|
+
DISABLING_COPS_CONTENT_TOKEN = %r{[A-Za-z]+/[A-Za-z]+|all}.freeze
|
20
20
|
|
21
21
|
def on_new_investigation
|
22
22
|
processed_source.comments.each do |comment|
|
@@ -67,7 +67,7 @@ module RuboCop
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def contain_unexpected_character_for_department_name?(name)
|
70
|
-
name.match?(%r{[^A-z/, ]})
|
70
|
+
name.match?(%r{[^A-Za-z/, ]})
|
71
71
|
end
|
72
72
|
|
73
73
|
def qualified_legacy_cop_name(cop_name)
|
@@ -25,7 +25,7 @@ module RuboCop
|
|
25
25
|
def comments_contain_disables?(node, cop_name)
|
26
26
|
disabled_ranges = processed_source.disabled_line_ranges[cop_name]
|
27
27
|
|
28
|
-
return unless disabled_ranges
|
28
|
+
return false unless disabled_ranges
|
29
29
|
|
30
30
|
node_range = node.source_range.line...find_end_line(node)
|
31
31
|
|
@@ -109,7 +109,7 @@ module RuboCop
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def use_block_argument_as_local_variable?(node, last_argument)
|
112
|
-
return if node.body.nil?
|
112
|
+
return false if node.body.nil?
|
113
113
|
|
114
114
|
node.body.each_descendant(:lvar, :lvasgn).any? do |lvar|
|
115
115
|
!lvar.parent.block_pass_type? && lvar.node_parts[0].to_s == last_argument
|
@@ -204,14 +204,14 @@ module RuboCop
|
|
204
204
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
205
205
|
def on_defined?(node)
|
206
206
|
arg = node.arguments.first
|
207
|
-
return unless arg.ivar_type?
|
207
|
+
return false unless arg.ivar_type?
|
208
208
|
|
209
209
|
method_node, method_name = find_definition(node)
|
210
|
-
return unless method_node
|
210
|
+
return false unless method_node
|
211
211
|
|
212
212
|
var_name = arg.children.first
|
213
213
|
defined_memoized?(method_node.body, var_name) do |defined_ivar, return_ivar, ivar_assign|
|
214
|
-
return if matches?(method_name, ivar_assign)
|
214
|
+
return false if matches?(method_name, ivar_assign)
|
215
215
|
|
216
216
|
suggested_var = suggested_var(method_name)
|
217
217
|
msg = format(
|
@@ -35,7 +35,7 @@ module RuboCop
|
|
35
35
|
unless contents.empty?
|
36
36
|
corrector.replace(
|
37
37
|
contents,
|
38
|
-
contents.source.gsub(/\A/, '# ').gsub(
|
38
|
+
contents.source.gsub(/\A/, '# ').gsub("\n\n", "\n#\n").gsub(/\n(?=[^#])/, "\n# ")
|
39
39
|
)
|
40
40
|
end
|
41
41
|
corrector.remove(eq_end)
|
@@ -411,7 +411,7 @@ module RuboCop
|
|
411
411
|
end
|
412
412
|
|
413
413
|
def correction_would_break_code?(node)
|
414
|
-
return unless node.keywords?
|
414
|
+
return false unless node.keywords?
|
415
415
|
|
416
416
|
node.send_node.arguments? && !node.send_node.parenthesized?
|
417
417
|
end
|
@@ -433,7 +433,7 @@ module RuboCop
|
|
433
433
|
end
|
434
434
|
|
435
435
|
def return_value_used?(node)
|
436
|
-
return unless node.parent
|
436
|
+
return false unless node.parent
|
437
437
|
|
438
438
|
# If there are parentheses around the block, check if that
|
439
439
|
# is being used.
|
@@ -445,7 +445,7 @@ module RuboCop
|
|
445
445
|
end
|
446
446
|
|
447
447
|
def return_value_of_scope?(node)
|
448
|
-
return unless node.parent
|
448
|
+
return false unless node.parent
|
449
449
|
|
450
450
|
conditional?(node.parent) || array_or_range?(node.parent) ||
|
451
451
|
node.parent.children.last == node
|
@@ -361,7 +361,7 @@ module RuboCop
|
|
361
361
|
end
|
362
362
|
|
363
363
|
def assignment_types_match?(*nodes)
|
364
|
-
return unless assignment_type?(nodes.first)
|
364
|
+
return false unless assignment_type?(nodes.first)
|
365
365
|
|
366
366
|
nodes.map(&:type).uniq.one?
|
367
367
|
end
|
@@ -440,6 +440,8 @@ module RuboCop
|
|
440
440
|
module ConditionalCorrectorHelper
|
441
441
|
def remove_whitespace_in_branches(corrector, branch, condition, column)
|
442
442
|
branch.each_node do |child|
|
443
|
+
next if child.source_range.nil?
|
444
|
+
|
443
445
|
white_space = white_space_range(child, column)
|
444
446
|
corrector.remove(white_space) if white_space.source.strip.empty?
|
445
447
|
end
|
@@ -108,7 +108,7 @@ module RuboCop
|
|
108
108
|
comments = heredoc_comment_blocks(arg_node.loc.heredoc_body.line_span)
|
109
109
|
.concat(preceding_comment_blocks(arg_node.parent))
|
110
110
|
|
111
|
-
return if comments.none?
|
111
|
+
return false if comments.none?
|
112
112
|
|
113
113
|
regexp = comment_regexp(arg_node)
|
114
114
|
comments.any?(regexp) || regexp.match?(comments.join)
|
@@ -158,13 +158,16 @@ module RuboCop
|
|
158
158
|
return false unless expressions.size >= 1 && unique_expressions.one?
|
159
159
|
|
160
160
|
unique_expression = unique_expressions.first
|
161
|
-
return true unless unique_expression
|
161
|
+
return true unless unique_expression&.assignment?
|
162
162
|
|
163
163
|
lhs = unique_expression.child_nodes.first
|
164
164
|
node.condition.child_nodes.none? { |n| n.source == lhs.source if n.variable? }
|
165
165
|
end
|
166
166
|
|
167
|
-
|
167
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
168
|
+
def check_expressions(node, expressions, insert_position)
|
169
|
+
return if expressions.any?(&:nil?)
|
170
|
+
|
168
171
|
inserted_expression = false
|
169
172
|
|
170
173
|
expressions.each do |expression|
|
@@ -184,6 +187,7 @@ module RuboCop
|
|
184
187
|
end
|
185
188
|
end
|
186
189
|
end
|
190
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
187
191
|
|
188
192
|
def last_child_of_parent?(node)
|
189
193
|
return true unless (parent = node.parent)
|
@@ -98,7 +98,7 @@ module RuboCop
|
|
98
98
|
|
99
99
|
def call_in_literals?(node)
|
100
100
|
parent = node.parent&.block_type? ? node.parent.parent : node.parent
|
101
|
-
return unless parent
|
101
|
+
return false unless parent
|
102
102
|
|
103
103
|
parent.pair_type? ||
|
104
104
|
parent.array_type? ||
|
@@ -109,7 +109,7 @@ module RuboCop
|
|
109
109
|
|
110
110
|
def call_in_logical_operators?(node)
|
111
111
|
parent = node.parent&.block_type? ? node.parent.parent : node.parent
|
112
|
-
return unless parent
|
112
|
+
return false unless parent
|
113
113
|
|
114
114
|
logical_operator?(parent) ||
|
115
115
|
(parent.send_type? &&
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Checks for uses a redundant current directory in path.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
#
|
10
|
+
# # bad
|
11
|
+
# require_relative './path/to/feature'
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# require_relative 'path/to/feature'
|
15
|
+
#
|
16
|
+
class RedundantCurrentDirectoryInPath < Base
|
17
|
+
include RangeHelp
|
18
|
+
extend AutoCorrector
|
19
|
+
|
20
|
+
MSG = 'Remove the redundant current directory path.'
|
21
|
+
CURRENT_DIRECTORY_PATH = './'
|
22
|
+
|
23
|
+
def on_send(node)
|
24
|
+
return unless node.method?(:require_relative)
|
25
|
+
return unless node.first_argument.str_content&.start_with?(CURRENT_DIRECTORY_PATH)
|
26
|
+
return unless (index = node.first_argument.source.index(CURRENT_DIRECTORY_PATH))
|
27
|
+
|
28
|
+
begin_pos = node.first_argument.source_range.begin.begin_pos + index
|
29
|
+
range = range_between(begin_pos, begin_pos + 2)
|
30
|
+
|
31
|
+
add_offense(range) do |corrector|
|
32
|
+
corrector.remove(range)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -116,7 +116,7 @@ module RuboCop
|
|
116
116
|
return false if argument_newline?(node)
|
117
117
|
|
118
118
|
source = node.parent ? node.parent.source : node.source
|
119
|
-
parse(source.gsub(
|
119
|
+
parse(source.gsub("\\\n", "\n")).valid_syntax?
|
120
120
|
end
|
121
121
|
|
122
122
|
def inside_string_literal?(range, token)
|
@@ -150,7 +150,7 @@ module RuboCop
|
|
150
150
|
end
|
151
151
|
|
152
152
|
def same_line?(node, line)
|
153
|
-
return unless (source_range = node.source_range)
|
153
|
+
return false unless (source_range = node.source_range)
|
154
154
|
|
155
155
|
if node.is_a?(AST::StrNode)
|
156
156
|
if node.heredoc?
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Style
|
6
|
+
# Identifies places where argument can be replaced from
|
7
|
+
# a deterministic regexp to a string.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# 'foo'.byteindex(/f/)
|
12
|
+
# 'foo'.byterindex(/f/)
|
13
|
+
# 'foo'.gsub(/f/, 'x')
|
14
|
+
# 'foo'.gsub!(/f/, 'x')
|
15
|
+
# 'foo'.partition(/f/)
|
16
|
+
# 'foo'.rpartition(/f/)
|
17
|
+
# 'foo'.scan(/f/)
|
18
|
+
# 'foo'.split(/f/)
|
19
|
+
# 'foo'.start_with?(/f/)
|
20
|
+
# 'foo'.sub(/f/, 'x')
|
21
|
+
# 'foo'.sub!(/f/, 'x')
|
22
|
+
#
|
23
|
+
# # good
|
24
|
+
# 'foo'.byteindex('f')
|
25
|
+
# 'foo'.byterindex('f')
|
26
|
+
# 'foo'.gsub('f', 'x')
|
27
|
+
# 'foo'.gsub!('f', 'x')
|
28
|
+
# 'foo'.partition('f')
|
29
|
+
# 'foo'.rpartition('f')
|
30
|
+
# 'foo'.scan('f')
|
31
|
+
# 'foo'.split('f')
|
32
|
+
# 'foo'.start_with?('f')
|
33
|
+
# 'foo'.sub('f', 'x')
|
34
|
+
# 'foo'.sub!('f', 'x')
|
35
|
+
class RedundantRegexpArgument < Base
|
36
|
+
extend AutoCorrector
|
37
|
+
|
38
|
+
MSG = 'Use string `%<prefer>s` as argument instead of regexp `%<current>s`.'
|
39
|
+
RESTRICT_ON_SEND = %i[
|
40
|
+
byteindex byterindex gsub gsub! partition rpartition scan split start_with? sub sub!
|
41
|
+
].freeze
|
42
|
+
DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze
|
43
|
+
STR_SPECIAL_CHARS = %w[\n \" \' \\\\ \t \b \f \r].freeze
|
44
|
+
|
45
|
+
def on_send(node)
|
46
|
+
return unless (regexp_node = node.first_argument)
|
47
|
+
return unless regexp_node.regexp_type?
|
48
|
+
return if !regexp_node.regopt.children.empty? || regexp_node.content == ' '
|
49
|
+
return unless determinist_regexp?(regexp_node)
|
50
|
+
|
51
|
+
prefer = preferred_argument(regexp_node)
|
52
|
+
message = format(MSG, prefer: prefer, current: regexp_node.source)
|
53
|
+
|
54
|
+
add_offense(regexp_node, message: message) do |corrector|
|
55
|
+
corrector.replace(regexp_node, prefer)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def determinist_regexp?(regexp_node)
|
62
|
+
DETERMINISTIC_REGEX.match?(regexp_node.source)
|
63
|
+
end
|
64
|
+
|
65
|
+
def preferred_argument(regexp_node)
|
66
|
+
new_argument = replacement(regexp_node)
|
67
|
+
|
68
|
+
if new_argument.include?('"')
|
69
|
+
new_argument.gsub!("'", "\\\\'")
|
70
|
+
quote = "'"
|
71
|
+
else
|
72
|
+
quote = '"'
|
73
|
+
end
|
74
|
+
|
75
|
+
"#{quote}#{new_argument}#{quote}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def replacement(regexp_node)
|
79
|
+
regexp_content = regexp_node.content
|
80
|
+
stack = []
|
81
|
+
chars = regexp_content.chars.each_with_object([]) do |char, strings|
|
82
|
+
if stack.empty? && char == '\\'
|
83
|
+
stack.push(char)
|
84
|
+
else
|
85
|
+
strings << "#{stack.pop}#{char}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
chars.map do |char|
|
89
|
+
char = char.dup
|
90
|
+
char.delete!('\\') unless STR_SPECIAL_CHARS.include?(char)
|
91
|
+
char
|
92
|
+
end.join
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -44,7 +44,8 @@ module RuboCop
|
|
44
44
|
|
45
45
|
def on_regexp(node)
|
46
46
|
each_escape(node) do |char, index, within_character_class|
|
47
|
-
next if allowed_escape?(node, char, index,
|
47
|
+
next if char.valid_encoding? && allowed_escape?(node, char, index,
|
48
|
+
within_character_class)
|
48
49
|
|
49
50
|
location = escape_range_at_index(node, index)
|
50
51
|
|