rubocop 1.89.0 → 1.90.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 +4 -4
- data/config/default.yml +74 -7
- data/exe/rubocop +1 -1
- data/lib/rubocop/cached_data.rb +7 -2
- data/lib/rubocop/cli/command/execute_runner.rb +31 -0
- data/lib/rubocop/cli.rb +2 -4
- data/lib/rubocop/comment_config/directive_range.rb +32 -0
- data/lib/rubocop/comment_config/disable_next.rb +97 -0
- data/lib/rubocop/comment_config.rb +46 -30
- data/lib/rubocop/config.rb +23 -5
- data/lib/rubocop/config_loader.rb +1 -2
- data/lib/rubocop/config_loader_resolver.rb +1 -2
- data/lib/rubocop/config_validator.rb +1 -2
- data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
- data/lib/rubocop/cop/base.rb +34 -2
- data/lib/rubocop/cop/corrector.rb +3 -3
- data/lib/rubocop/cop/internal_affairs/node_matcher_directive.rb +1 -2
- data/lib/rubocop/cop/internal_affairs/node_pattern_groups/ast_walker.rb +1 -2
- data/lib/rubocop/cop/layout/block_alignment.rb +1 -2
- data/lib/rubocop/cop/layout/class_structure.rb +102 -26
- data/lib/rubocop/cop/layout/empty_line_after_guard_clause.rb +1 -2
- data/lib/rubocop/cop/layout/empty_line_after_magic_comment.rb +63 -5
- data/lib/rubocop/cop/layout/end_alignment.rb +1 -2
- data/lib/rubocop/cop/layout/extra_spacing.rb +105 -3
- data/lib/rubocop/cop/layout/first_argument_indentation.rb +21 -1
- data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
- data/lib/rubocop/cop/layout/line_length.rb +1 -2
- data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +5 -5
- data/lib/rubocop/cop/layout/rescue_ensure_alignment.rb +1 -2
- data/lib/rubocop/cop/layout/space_around_operators.rb +7 -0
- data/lib/rubocop/cop/layout/space_inside_array_literal_brackets.rb +2 -4
- data/lib/rubocop/cop/lint/ambiguous_range.rb +1 -2
- data/lib/rubocop/cop/lint/argument_mismatch.rb +89 -0
- data/lib/rubocop/cop/lint/constant_reassignment.rb +17 -11
- data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -8
- data/lib/rubocop/cop/lint/deprecated_reference.rb +11 -2
- data/lib/rubocop/cop/lint/duplicate_methods.rb +112 -14
- data/lib/rubocop/cop/lint/implicit_string_concatenation.rb +1 -2
- data/lib/rubocop/cop/lint/ineffective_access_modifier.rb +1 -2
- data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
- data/lib/rubocop/cop/lint/literal_as_condition.rb +3 -6
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +3 -6
- data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
- data/lib/rubocop/cop/lint/name_typo.rb +53 -6
- data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -0
- data/lib/rubocop/cop/lint/redundant_cop_disable_directive.rb +98 -19
- data/lib/rubocop/cop/lint/redundant_cop_enable_directive.rb +47 -7
- data/lib/rubocop/cop/lint/redundant_require_statement.rb +1 -2
- data/lib/rubocop/cop/lint/redundant_safe_navigation.rb +1 -2
- data/lib/rubocop/cop/lint/redundant_splat_expansion.rb +1 -2
- data/lib/rubocop/cop/lint/redundant_type_conversion.rb +1 -2
- data/lib/rubocop/cop/lint/redundant_with_index.rb +1 -2
- data/lib/rubocop/cop/lint/safe_navigation_chain.rb +1 -2
- data/lib/rubocop/cop/lint/safe_navigation_consistency.rb +2 -4
- data/lib/rubocop/cop/lint/super_argument_mismatch.rb +179 -0
- data/lib/rubocop/cop/lint/suppressed_exception_in_number_conversion.rb +1 -2
- data/lib/rubocop/cop/lint/to_enum_arguments.rb +1 -2
- data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
- data/lib/rubocop/cop/lint/unused_private_method.rb +94 -17
- data/lib/rubocop/cop/lint/useless_access_modifier.rb +1 -2
- data/lib/rubocop/cop/lint/useless_assignment.rb +1 -2
- data/lib/rubocop/cop/lint/useless_constant_scoping.rb +36 -0
- data/lib/rubocop/cop/lint/useless_method_definition.rb +1 -1
- data/lib/rubocop/cop/lint/useless_rescue.rb +1 -2
- data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
- data/lib/rubocop/cop/lint/void.rb +1 -2
- data/lib/rubocop/cop/lint.rb +2 -0
- data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
- data/lib/rubocop/cop/migration/department_name.rb +1 -2
- data/lib/rubocop/cop/mixin/comments_help.rb +1 -2
- data/lib/rubocop/cop/mixin/configurable_enforced_style.rb +1 -2
- data/lib/rubocop/cop/mixin/hash_shorthand_syntax.rb +1 -2
- data/lib/rubocop/cop/mixin/hash_subset.rb +1 -2
- data/lib/rubocop/cop/mixin/indexed_method_arity.rb +94 -0
- data/lib/rubocop/cop/mixin/line_length_help.rb +7 -1
- data/lib/rubocop/cop/mixin/preceding_following_alignment.rb +51 -6
- data/lib/rubocop/cop/mixin/range_help.rb +1 -2
- data/lib/rubocop/cop/mixin/statement_modifier.rb +13 -7
- data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
- data/lib/rubocop/cop/mixin.rb +4 -0
- data/lib/rubocop/cop/naming/binary_operator_parameter_name.rb +16 -0
- data/lib/rubocop/cop/naming/memoized_instance_variable_name.rb +2 -4
- data/lib/rubocop/cop/naming/method_name.rb +5 -2
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
- data/lib/rubocop/cop/offense.rb +27 -4
- data/lib/rubocop/cop/registry.rb +34 -10
- data/lib/rubocop/cop/style/accessor_grouping.rb +1 -2
- data/lib/rubocop/cop/style/arguments_forwarding.rb +3 -6
- data/lib/rubocop/cop/style/array_first_last.rb +1 -2
- data/lib/rubocop/cop/style/bitwise_predicate.rb +1 -2
- data/lib/rubocop/cop/style/block_delimiters.rb +3 -6
- data/lib/rubocop/cop/style/case_like_if.rb +2 -4
- data/lib/rubocop/cop/style/class_and_module_children.rb +1 -2
- data/lib/rubocop/cop/style/combinable_loops.rb +1 -2
- data/lib/rubocop/cop/style/concat_array_literals.rb +1 -2
- data/lib/rubocop/cop/style/conditional_assignment.rb +1 -2
- data/lib/rubocop/cop/style/constant_visibility.rb +1 -2
- data/lib/rubocop/cop/style/copyright.rb +1 -2
- data/lib/rubocop/cop/style/directive_scope.rb +171 -0
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +36 -10
- data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
- data/lib/rubocop/cop/style/format_string_token.rb +1 -2
- data/lib/rubocop/cop/style/guard_clause.rb +1 -2
- data/lib/rubocop/cop/style/hash_each_methods.rb +1 -2
- data/lib/rubocop/cop/style/hash_syntax.rb +1 -2
- data/lib/rubocop/cop/style/identical_conditional_branches.rb +2 -4
- data/lib/rubocop/cop/style/if_inside_else.rb +1 -2
- data/lib/rubocop/cop/style/if_unless_modifier.rb +1 -2
- data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
- data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +2 -4
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
- data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
- data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
- data/lib/rubocop/cop/style/negative_array_index.rb +2 -4
- data/lib/rubocop/cop/style/nil_comparison.rb +1 -2
- data/lib/rubocop/cop/style/operator_method_call.rb +1 -2
- data/lib/rubocop/cop/style/parallel_assignment.rb +1 -2
- data/lib/rubocop/cop/style/redundant_assignment.rb +1 -2
- data/lib/rubocop/cop/style/redundant_condition.rb +2 -4
- data/lib/rubocop/cop/style/redundant_double_splat_hash_braces.rb +1 -2
- data/lib/rubocop/cop/style/redundant_each.rb +1 -2
- data/lib/rubocop/cop/style/redundant_format.rb +2 -4
- data/lib/rubocop/cop/style/redundant_parentheses.rb +2 -4
- data/lib/rubocop/cop/style/redundant_regexp_argument.rb +1 -2
- data/lib/rubocop/cop/style/redundant_return.rb +1 -2
- data/lib/rubocop/cop/style/redundant_self_assignment.rb +1 -2
- data/lib/rubocop/cop/style/redundant_string_escape.rb +1 -2
- data/lib/rubocop/cop/style/rescue_modifier.rb +1 -2
- data/lib/rubocop/cop/style/safe_navigation.rb +1 -2
- data/lib/rubocop/cop/style/sample.rb +11 -6
- data/lib/rubocop/cop/style/select_by_regexp.rb +1 -2
- data/lib/rubocop/cop/style/semicolon.rb +2 -4
- data/lib/rubocop/cop/style/single_line_do_end_block.rb +1 -2
- data/lib/rubocop/cop/style/sole_nested_conditional.rb +1 -2
- data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
- data/lib/rubocop/cop/style/string_concatenation.rb +18 -5
- data/lib/rubocop/cop/style/super_arguments.rb +1 -2
- data/lib/rubocop/cop/style/symbol_proc.rb +1 -2
- data/lib/rubocop/cop/style/time_now.rb +41 -0
- data/lib/rubocop/cop/style/yoda_condition.rb +1 -2
- data/lib/rubocop/cop/style.rb +2 -0
- data/lib/rubocop/cop/team.rb +11 -3
- data/lib/rubocop/cop/util.rb +2 -4
- data/lib/rubocop/cop/variable_force/variable.rb +1 -2
- data/lib/rubocop/cops_documentation_generator.rb +3 -6
- data/lib/rubocop/directive_comment.rb +37 -3
- data/lib/rubocop/formatter/emacs_style_formatter.rb +3 -1
- data/lib/rubocop/formatter/html_formatter.rb +2 -4
- data/lib/rubocop/formatter/json_formatter.rb +11 -6
- data/lib/rubocop/formatter/junit_formatter.rb +26 -9
- data/lib/rubocop/formatter/offense_count_formatter.rb +1 -2
- data/lib/rubocop/formatter/simple_text_formatter.rb +4 -3
- data/lib/rubocop/formatter/tap_formatter.rb +3 -1
- data/lib/rubocop/formatter/worst_offenders_formatter.rb +1 -2
- data/lib/rubocop/lockfile.rb +18 -0
- data/lib/rubocop/lsp/diagnostic.rb +4 -8
- data/lib/rubocop/lsp/routes.rb +48 -19
- data/lib/rubocop/mcp/server.rb +1 -2
- data/lib/rubocop/options.rb +10 -6
- data/lib/rubocop/path_util.rb +1 -2
- data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
- data/lib/rubocop/rake_task.rb +1 -2
- data/lib/rubocop/rspec/expect_offense.rb +2 -4
- data/lib/rubocop/rspec/shared_contexts.rb +4 -2
- data/lib/rubocop/runner.rb +26 -23
- data/lib/rubocop/server/cache.rb +1 -2
- data/lib/rubocop/server/cli.rb +2 -4
- data/lib/rubocop/server.rb +14 -0
- data/lib/rubocop/target_finder.rb +4 -1
- data/lib/rubocop/todo_audit.rb +111 -0
- data/lib/rubocop/version.rb +3 -5
- data/lib/rubocop.rb +3 -0
- data/lib/ruby_lsp/rubocop/addon.rb +1 -2
- metadata +13 -5
|
@@ -42,29 +42,70 @@ module RuboCop
|
|
|
42
42
|
# # good
|
|
43
43
|
# # rubocop:disable Layout/LineLength -- comment
|
|
44
44
|
#
|
|
45
|
+
# # bad
|
|
46
|
+
# # rucocop:disable Layout/LineLength
|
|
47
|
+
#
|
|
48
|
+
# # good
|
|
49
|
+
# # rubocop:disable Layout/LineLength
|
|
50
|
+
#
|
|
51
|
+
# # bad
|
|
52
|
+
# # rubocop:disable Layout/LineLenght
|
|
53
|
+
#
|
|
54
|
+
# # good
|
|
55
|
+
# # rubocop:disable Layout/LineLength
|
|
56
|
+
#
|
|
45
57
|
class CopDirectiveSyntax < Base
|
|
46
58
|
COMMON_MSG = 'Malformed directive comment detected.'
|
|
47
59
|
|
|
48
60
|
MISSING_MODE_NAME_MSG = 'The mode name is missing.'
|
|
49
|
-
INVALID_MODE_NAME_MSG = 'The mode name must be one of `enable`, `disable`, `todo`, `push`, or `pop`.' # rubocop:disable Layout/LineLength
|
|
61
|
+
INVALID_MODE_NAME_MSG = 'The mode name must be one of `enable`, `disable`, `disable-next`, `todo`, `todo-next`, `push`, or `pop`.' # rubocop:disable Layout/LineLength
|
|
50
62
|
MISSING_COP_NAME_MSG = 'The cop name is missing.'
|
|
51
63
|
MALFORMED_COP_NAMES_MSG = 'Cop names must be separated by commas. ' \
|
|
52
64
|
'Comment in the directive must start with `--`.'
|
|
65
|
+
NEXT_DIRECTIVE_AT_EOL_MSG = 'A `-next` directive must be on its own line, above the ' \
|
|
66
|
+
'statement it applies to.'
|
|
67
|
+
INVALID_KEYWORD_MSG = 'The directive keyword must be `rubocop`, not `%<keyword>s`.'
|
|
68
|
+
UNKNOWN_COP_MSG = 'Unknown cop name `%<name>s`%<suggestion>s.'
|
|
69
|
+
|
|
70
|
+
# A comment that looks like an attempted directive: any keyword
|
|
71
|
+
# followed by a colon and a valid mode name.
|
|
72
|
+
NEAR_MISS_KEYWORD_REGEXP = /
|
|
73
|
+
\A\#+\s*(?<keyword>[A-Za-z][\w-]*)\s*:\s*
|
|
74
|
+
(?:#{DirectiveComment::MODES_PATTERN})\b
|
|
75
|
+
/x.freeze
|
|
53
76
|
|
|
54
77
|
def on_new_investigation
|
|
55
78
|
processed_source.comments.each do |comment|
|
|
56
79
|
directive_comment = DirectiveComment.new(comment)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
if directive_comment.start_with_marker?
|
|
81
|
+
check_directive(comment, directive_comment)
|
|
82
|
+
elsif (keyword = near_miss_keyword(comment))
|
|
83
|
+
message = format("#{COMMON_MSG} #{INVALID_KEYWORD_MSG}", keyword: keyword)
|
|
84
|
+
add_offense(comment, message: message)
|
|
85
|
+
end
|
|
62
86
|
end
|
|
63
87
|
end
|
|
64
88
|
|
|
65
89
|
private
|
|
66
90
|
|
|
67
|
-
|
|
91
|
+
def check_directive(comment, directive_comment)
|
|
92
|
+
if directive_comment.malformed?
|
|
93
|
+
add_offense(comment, message: offense_message(directive_comment))
|
|
94
|
+
elsif misplaced_next_directive?(directive_comment)
|
|
95
|
+
add_offense(comment, message: NEXT_DIRECTIVE_AT_EOL_MSG)
|
|
96
|
+
elsif (name = unknown_cop_name(directive_comment))
|
|
97
|
+
add_offense(comment, message: unknown_cop_message(directive_comment, name))
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# An EOL `disable-next` is not honored - it must fail loudly instead
|
|
102
|
+
# of silently doing nothing.
|
|
103
|
+
def misplaced_next_directive?(directive_comment)
|
|
104
|
+
directive_comment.disable_next? &&
|
|
105
|
+
!processed_source.comment_config.comment_only_line?(directive_comment.line_number)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# rubocop:disable-next Metrics/MethodLength
|
|
68
109
|
def offense_message(directive_comment)
|
|
69
110
|
comment = directive_comment.comment
|
|
70
111
|
after_marker = comment.text.sub(DirectiveComment::DIRECTIVE_MARKER_REGEXP, '')
|
|
@@ -81,7 +122,42 @@ module RuboCop
|
|
|
81
122
|
|
|
82
123
|
"#{COMMON_MSG} #{additional_msg}"
|
|
83
124
|
end
|
|
84
|
-
|
|
125
|
+
|
|
126
|
+
def near_miss_keyword(comment)
|
|
127
|
+
match = comment.text.match(NEAR_MISS_KEYWORD_REGEXP)
|
|
128
|
+
return unless match
|
|
129
|
+
|
|
130
|
+
keyword = match[:keyword]
|
|
131
|
+
return if keyword == 'rubocop'
|
|
132
|
+
|
|
133
|
+
keyword if similar_to_rubocop?(keyword)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def similar_to_rubocop?(keyword)
|
|
137
|
+
return true if keyword.casecmp?('rubocop')
|
|
138
|
+
# `DidYouMean` is not always available - see `NameSimilarity`.
|
|
139
|
+
return false unless defined?(DidYouMean::Levenshtein)
|
|
140
|
+
|
|
141
|
+
DidYouMean::Levenshtein.distance(keyword.downcase, 'rubocop') <= 2
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def unknown_cop_name(directive_comment)
|
|
145
|
+
return if directive_comment.push? || directive_comment.pop? || directive_comment.all_cops?
|
|
146
|
+
|
|
147
|
+
registry = directive_comment.cop_registry
|
|
148
|
+
directive_comment.raw_cop_names.find do |name|
|
|
149
|
+
next false if registry.department?(name)
|
|
150
|
+
|
|
151
|
+
qualified = registry.qualified_cop_name(name, nil, warn: false)
|
|
152
|
+
!registry.contains_cop_matching?([qualified])
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def unknown_cop_message(directive_comment, name)
|
|
157
|
+
similar = NameSimilarity.find_similar_name(name, directive_comment.cop_registry.names)
|
|
158
|
+
suggestion = similar ? " (did you mean `#{similar}`?)" : ''
|
|
159
|
+
format(UNKNOWN_COP_MSG, name: name, suggestion: suggestion)
|
|
160
|
+
end
|
|
85
161
|
end
|
|
86
162
|
end
|
|
87
163
|
end
|
|
@@ -150,10 +150,19 @@ module RuboCop
|
|
|
150
150
|
false
|
|
151
151
|
end
|
|
152
152
|
|
|
153
|
+
# Enumerated in a single pass that stops at the first non-deprecated
|
|
154
|
+
# definition, since materializing every definition of a declaration
|
|
155
|
+
# that is almost never deprecated dominates the cop's cost.
|
|
153
156
|
def deprecated?(declaration)
|
|
154
|
-
|
|
157
|
+
any = false
|
|
155
158
|
|
|
156
|
-
definitions.
|
|
159
|
+
declaration.definitions.each do |definition|
|
|
160
|
+
return false unless definition.deprecated?
|
|
161
|
+
|
|
162
|
+
any = true
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
any
|
|
157
166
|
end
|
|
158
167
|
|
|
159
168
|
# References from a definition that is itself documented as deprecated
|
|
@@ -19,7 +19,22 @@ module RuboCop
|
|
|
19
19
|
# file is wrapped in a conditional, so a platform-specific redefinition in
|
|
20
20
|
# another file may still be reported. Aliasing the method to itself (see above)
|
|
21
21
|
# before redefining marks the redefinition as intentional and is respected
|
|
22
|
-
# across files.
|
|
22
|
+
# across files. With `AllCops/ActiveSupportExtensionsEnabled: true`, Active
|
|
23
|
+
# Support's redefinition markers (`silence_redefinition_of_method` and
|
|
24
|
+
# `redefine_method`) are honored the same way.
|
|
25
|
+
#
|
|
26
|
+
# NOTE: Methods defined with `define_method` are not recorded in the project
|
|
27
|
+
# index, so a duplicate whose other definition uses `define_method` cannot
|
|
28
|
+
# be detected across files.
|
|
29
|
+
#
|
|
30
|
+
# Cross-file duplicates whose other definition lives in a file matching one
|
|
31
|
+
# of the `AllowedCrossFilePaths` patterns are not reported. This suits files
|
|
32
|
+
# that redefine application methods but are never loaded together with them,
|
|
33
|
+
# such as standalone scripts. Patterns are matched with the same glob (or
|
|
34
|
+
# regexp) semantics as `Exclude`, against the other file's path relative to
|
|
35
|
+
# the directory of the `.rubocop.yml` that configures the cop; absolute
|
|
36
|
+
# patterns are matched against the absolute path. Offenses inside such files
|
|
37
|
+
# themselves are best silenced with an ordinary per-cop `Exclude`.
|
|
23
38
|
#
|
|
24
39
|
# @example
|
|
25
40
|
#
|
|
@@ -129,6 +144,24 @@ module RuboCop
|
|
|
129
144
|
# delegate :foo, to: :bar
|
|
130
145
|
# end
|
|
131
146
|
#
|
|
147
|
+
# # good - Active Support's redefinition markers signal an intentional
|
|
148
|
+
# # redefinition of a method defined in another file
|
|
149
|
+
# silence_redefinition_of_method :foo
|
|
150
|
+
# def foo
|
|
151
|
+
# 1
|
|
152
|
+
# end
|
|
153
|
+
#
|
|
154
|
+
# @example AllowedCrossFilePaths: ['script/**/*'] (default: [])
|
|
155
|
+
# # Cross-file duplicates whose other definition lives in a file
|
|
156
|
+
# # matching one of the patterns are not reported.
|
|
157
|
+
#
|
|
158
|
+
# # good - assuming `AppHelper#format` is also defined in
|
|
159
|
+
# # `script/backfill.rb`, which is never loaded with this file
|
|
160
|
+
# class AppHelper
|
|
161
|
+
# def format
|
|
162
|
+
# end
|
|
163
|
+
# end
|
|
164
|
+
#
|
|
132
165
|
# @example DelegatingMethods: ['delegate', 'expose'] (default: ['delegate'])
|
|
133
166
|
# # A project's own `delegate`-shaped macros can be registered so the
|
|
134
167
|
# # methods they define are recognized (with
|
|
@@ -160,13 +193,14 @@ module RuboCop
|
|
|
160
193
|
super
|
|
161
194
|
@definitions = {}
|
|
162
195
|
@scopes = Hash.new { |hash, key| hash[key] = [] }
|
|
163
|
-
@
|
|
196
|
+
@intentionally_redefined = Set.new
|
|
164
197
|
end
|
|
165
198
|
|
|
166
199
|
def on_new_investigation
|
|
167
|
-
# The self-alias trick
|
|
168
|
-
# the file that uses
|
|
169
|
-
|
|
200
|
+
# The self-alias trick and Active Support's redefinition markers declare
|
|
201
|
+
# an intentional redefinition only within the file that uses them, so the
|
|
202
|
+
# tracked names do not carry over.
|
|
203
|
+
@intentionally_redefined = Set.new
|
|
170
204
|
super
|
|
171
205
|
end
|
|
172
206
|
|
|
@@ -199,7 +233,7 @@ module RuboCop
|
|
|
199
233
|
return unless name && original_name
|
|
200
234
|
|
|
201
235
|
if name == original_name
|
|
202
|
-
|
|
236
|
+
track_intentional_redefinition(node, name)
|
|
203
237
|
return
|
|
204
238
|
end
|
|
205
239
|
return if node.ancestors.any?(&:if_type?)
|
|
@@ -241,6 +275,15 @@ module RuboCop
|
|
|
241
275
|
)
|
|
242
276
|
PATTERN
|
|
243
277
|
|
|
278
|
+
# Matches Active Support's markers of an intentional method redefinition:
|
|
279
|
+
# `silence_redefinition_of_method :name` suppresses Ruby's redefinition
|
|
280
|
+
# warning for a subsequent definition of `name`, and
|
|
281
|
+
# `redefine_method(:name) { ... }` both silences the warning and redefines.
|
|
282
|
+
# @!method active_support_redefinition_marker(node)
|
|
283
|
+
def_node_matcher :active_support_redefinition_marker, <<~PATTERN
|
|
284
|
+
(send nil? {:silence_redefinition_of_method :redefine_method} ({sym str} $_) ...)
|
|
285
|
+
PATTERN
|
|
286
|
+
|
|
244
287
|
# @!method sym_name(node)
|
|
245
288
|
def_node_matcher :sym_name, '(sym $_name)'
|
|
246
289
|
|
|
@@ -263,7 +306,7 @@ module RuboCop
|
|
|
263
306
|
|
|
264
307
|
if name && original_name
|
|
265
308
|
if name == original_name
|
|
266
|
-
|
|
309
|
+
track_intentional_redefinition(node, name)
|
|
267
310
|
return
|
|
268
311
|
end
|
|
269
312
|
return if inside_condition?(node)
|
|
@@ -283,6 +326,12 @@ module RuboCop
|
|
|
283
326
|
return if inside_condition?(node)
|
|
284
327
|
|
|
285
328
|
names.each { |name| found_instance_method(node, name) }
|
|
329
|
+
elsif (name = active_support_redefinition_marker(node))
|
|
330
|
+
# `redefine_method` takes a block; scope resolution must start from the
|
|
331
|
+
# block node, since `parent_module_name` cannot see through a block
|
|
332
|
+
# ancestor that is not a module constructor.
|
|
333
|
+
track_intentional_redefinition(node.block_node || node, name) if
|
|
334
|
+
active_support_extensions_enabled?
|
|
286
335
|
end
|
|
287
336
|
end
|
|
288
337
|
|
|
@@ -441,27 +490,39 @@ module RuboCop
|
|
|
441
490
|
if scope && !@scopes[scope].include?(key)
|
|
442
491
|
@definitions[key] = node
|
|
443
492
|
@scopes[scope] << key
|
|
444
|
-
elsif intentional_cross_file_redefinition?(node, method_name, key)
|
|
493
|
+
elsif intentional_cross_file_redefinition?(node, method_name, key) ||
|
|
494
|
+
allowed_cross_file_redefinition?(node, key)
|
|
445
495
|
@definitions[key] = node
|
|
446
496
|
else
|
|
447
497
|
add_offense(location(node), message: message_for_dup(node, method_name, key))
|
|
448
498
|
end
|
|
449
499
|
end
|
|
450
500
|
|
|
501
|
+
# `AllowedCrossFilePaths` also covers duplicates detected without the index,
|
|
502
|
+
# when the cop instance carries definitions over from an earlier file of the
|
|
503
|
+
# same run.
|
|
504
|
+
def allowed_cross_file_redefinition?(node, key)
|
|
505
|
+
other_path = @definitions[key].source_range.source_buffer.name
|
|
506
|
+
return false if other_path == node.source_range.source_buffer.name
|
|
507
|
+
|
|
508
|
+
allowed_cross_file_path?(other_path)
|
|
509
|
+
end
|
|
510
|
+
|
|
451
511
|
# The self-alias trick (`alias foo foo` or `alias_method :foo, :foo` right before
|
|
452
512
|
# a `def`) suppresses Ruby's method redefinition warning, signaling an intentional
|
|
453
|
-
# redefinition of a method defined in another file.
|
|
513
|
+
# redefinition of a method defined in another file. Active Support's redefinition
|
|
514
|
+
# markers (tracked when `ActiveSupportExtensionsEnabled` is on) signal the same.
|
|
454
515
|
def intentional_cross_file_redefinition?(node, method_name, key)
|
|
455
|
-
@
|
|
516
|
+
@intentionally_redefined.include?(method_name) &&
|
|
456
517
|
@definitions[key].source_range.source_buffer.name !=
|
|
457
518
|
node.source_range.source_buffer.name
|
|
458
519
|
end
|
|
459
520
|
|
|
460
|
-
def
|
|
521
|
+
def track_intentional_redefinition(node, name)
|
|
461
522
|
scope = node.parent_module_name
|
|
462
523
|
return unless scope
|
|
463
524
|
|
|
464
|
-
@
|
|
525
|
+
@intentionally_redefined << "#{humanize_scope(scope)}#{name}"
|
|
465
526
|
end
|
|
466
527
|
|
|
467
528
|
def method_key(node, method_name)
|
|
@@ -544,7 +605,7 @@ module RuboCop
|
|
|
544
605
|
|
|
545
606
|
def check_cross_file_duplicate(node, method_name)
|
|
546
607
|
return unless project_index
|
|
547
|
-
return if @
|
|
608
|
+
return if @intentionally_redefined.include?(method_name)
|
|
548
609
|
return if node.each_ancestor(:any_def).any?
|
|
549
610
|
return unless (prior = cross_file_prior_definition(method_name))
|
|
550
611
|
|
|
@@ -559,12 +620,27 @@ module RuboCop
|
|
|
559
620
|
|
|
560
621
|
definitions = definitions_in_other_files(
|
|
561
622
|
indexed_definitions(match[:owner], match[:separator], match[:name])
|
|
562
|
-
)
|
|
623
|
+
).reject { |definition| allowed_cross_file_path?(definition.location.to_file_path) }
|
|
563
624
|
return if definitions.empty? || cross_file_self_alias_trick?(definitions)
|
|
625
|
+
return if cross_file_redefinition_marker?(definitions, match[:name])
|
|
564
626
|
|
|
565
627
|
first_indexed_definition(definitions)
|
|
566
628
|
end
|
|
567
629
|
|
|
630
|
+
# Cross-file duplicates whose other definition site matches one of the
|
|
631
|
+
# `AllowedCrossFilePaths` patterns are skipped (e.g. standalone scripts that
|
|
632
|
+
# are never loaded together with the code they redefine). Patterns are
|
|
633
|
+
# matched like `Exclude` patterns, against the path relative to the
|
|
634
|
+
# directory of the config file that configures the cop; absolute patterns
|
|
635
|
+
# are matched against the absolute path.
|
|
636
|
+
def allowed_cross_file_path?(path)
|
|
637
|
+
patterns = cop_config.fetch('AllowedCrossFilePaths', [])
|
|
638
|
+
return false if patterns.empty?
|
|
639
|
+
|
|
640
|
+
relative = config.path_relative_to_config(path)
|
|
641
|
+
patterns.any? { |pattern| match_path?(pattern, relative) || match_path?(pattern, path) }
|
|
642
|
+
end
|
|
643
|
+
|
|
568
644
|
def indexed_definitions(owner, separator, name)
|
|
569
645
|
namespace = separator == '.' ? "#{owner}::<#{owner.split('::').last}>" : owner
|
|
570
646
|
|
|
@@ -601,6 +677,28 @@ module RuboCop
|
|
|
601
677
|
others.any? { |definition| alias_paths.include?(definition.location.to_file_path) }
|
|
602
678
|
end
|
|
603
679
|
|
|
680
|
+
# An Active Support redefinition marker alongside one of the definitions in
|
|
681
|
+
# another file marks the redefinition there as intentional, like the
|
|
682
|
+
# self-alias trick. The index does not record the marker's method-name
|
|
683
|
+
# argument, so the other file's source is searched for a marker naming
|
|
684
|
+
# the method.
|
|
685
|
+
def cross_file_redefinition_marker?(definitions, name)
|
|
686
|
+
return false unless active_support_extensions_enabled?
|
|
687
|
+
|
|
688
|
+
definitions.map { |definition| definition.location.to_file_path }.uniq.any? do |path|
|
|
689
|
+
redefinition_marker_in_file?(path, name)
|
|
690
|
+
end
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
def redefinition_marker_in_file?(path, name)
|
|
694
|
+
return false unless File.readable?(path)
|
|
695
|
+
|
|
696
|
+
escaped = Regexp.escape(name)
|
|
697
|
+
marker = /\b(?:silence_redefinition_of_method|redefine_method)\s*\(?\s*
|
|
698
|
+
(?::#{escaped}|(["'])#{escaped}\1)(?![\w=!?])/x
|
|
699
|
+
File.read(path).scrub.match?(marker)
|
|
700
|
+
end
|
|
701
|
+
|
|
604
702
|
def first_indexed_definition(definitions)
|
|
605
703
|
definitions.find { |definition| !definition.is_a?(Rubydex::MethodAliasDefinition) }
|
|
606
704
|
end
|
|
@@ -28,7 +28,7 @@ module RuboCop
|
|
|
28
28
|
FOR_METHOD = ' Or, if they were intended to be separate method ' \
|
|
29
29
|
'arguments, separate them with a comma.'
|
|
30
30
|
|
|
31
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
31
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
32
32
|
def on_dstr(node)
|
|
33
33
|
each_bad_cons(node) do |lhs_node, rhs_node|
|
|
34
34
|
range = lhs_node.source_range.join(rhs_node.source_range)
|
|
@@ -52,7 +52,6 @@ module RuboCop
|
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
56
55
|
|
|
57
56
|
private
|
|
58
57
|
|
|
@@ -80,7 +80,7 @@ module RuboCop
|
|
|
80
80
|
alternative: alternative)
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
83
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
84
84
|
def ineffective_modifier(node, ignored_methods = nil, modifier = nil, &block)
|
|
85
85
|
node.each_child_node do |child|
|
|
86
86
|
case child.type
|
|
@@ -97,7 +97,6 @@ module RuboCop
|
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
101
100
|
|
|
102
101
|
def access_modifier?(node)
|
|
103
102
|
node.bare_access_modifier? && !node.method?(:module_function)
|
|
@@ -41,7 +41,7 @@ module RuboCop
|
|
|
41
41
|
|
|
42
42
|
private
|
|
43
43
|
|
|
44
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
44
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
45
45
|
def check(node)
|
|
46
46
|
return if node.parent&.regexp_type?
|
|
47
47
|
return unless /(?<!\\)#\{.*\}/.match?(node.source)
|
|
@@ -51,7 +51,6 @@ module RuboCop
|
|
|
51
51
|
|
|
52
52
|
add_offense(node) { |corrector| autocorrect(corrector, node) }
|
|
53
53
|
end
|
|
54
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
55
54
|
|
|
56
55
|
def autocorrect(corrector, node)
|
|
57
56
|
starting_token, ending_token = if node.source.include?('"')
|
|
@@ -88,7 +88,7 @@ module RuboCop
|
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
# rubocop:disable Metrics/AbcSize
|
|
91
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
92
92
|
def on_while_post(node)
|
|
93
93
|
return if node.condition.source == 'true'
|
|
94
94
|
|
|
@@ -102,7 +102,6 @@ module RuboCop
|
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
|
-
# rubocop:enable Metrics/AbcSize
|
|
106
105
|
|
|
107
106
|
def on_until(node)
|
|
108
107
|
return if node.condition.source == 'false'
|
|
@@ -118,7 +117,7 @@ module RuboCop
|
|
|
118
117
|
end
|
|
119
118
|
end
|
|
120
119
|
|
|
121
|
-
# rubocop:disable Metrics/AbcSize
|
|
120
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
122
121
|
def on_until_post(node)
|
|
123
122
|
return if node.condition.source == 'false'
|
|
124
123
|
|
|
@@ -132,7 +131,6 @@ module RuboCop
|
|
|
132
131
|
end
|
|
133
132
|
end
|
|
134
133
|
end
|
|
135
|
-
# rubocop:enable Metrics/AbcSize
|
|
136
134
|
|
|
137
135
|
def on_case(case_node)
|
|
138
136
|
if (cond = case_node.condition)
|
|
@@ -250,7 +248,7 @@ module RuboCop
|
|
|
250
248
|
end
|
|
251
249
|
end
|
|
252
250
|
|
|
253
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
251
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
254
252
|
def correct_if_node(node, cond)
|
|
255
253
|
result = condition_evaluation?(node, cond)
|
|
256
254
|
|
|
@@ -281,7 +279,6 @@ module RuboCop
|
|
|
281
279
|
ignore_node(node)
|
|
282
280
|
end
|
|
283
281
|
end
|
|
284
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
285
282
|
end
|
|
286
283
|
end
|
|
287
284
|
end
|
|
@@ -24,7 +24,7 @@ module RuboCop
|
|
|
24
24
|
MSG = 'Literal interpolation detected.'
|
|
25
25
|
COMPOSITE = %i[array hash pair irange erange].freeze
|
|
26
26
|
|
|
27
|
-
# rubocop:disable Metrics/AbcSize
|
|
27
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
28
28
|
def on_interpolation(begin_node)
|
|
29
29
|
final_node = begin_node.children.last
|
|
30
30
|
return unless offending?(final_node)
|
|
@@ -50,7 +50,6 @@ module RuboCop
|
|
|
50
50
|
corrector.replace(final_node.parent, replacement)
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
|
-
# rubocop:enable Metrics/AbcSize
|
|
54
53
|
|
|
55
54
|
private
|
|
56
55
|
|
|
@@ -74,7 +73,7 @@ module RuboCop
|
|
|
74
73
|
node.array_type? && grandparent.regexp_type?
|
|
75
74
|
end
|
|
76
75
|
|
|
77
|
-
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
|
76
|
+
# rubocop:disable-next Metrics/MethodLength, Metrics/CyclomaticComplexity
|
|
78
77
|
def autocorrected_value(node)
|
|
79
78
|
case node.type
|
|
80
79
|
when :int
|
|
@@ -95,7 +94,6 @@ module RuboCop
|
|
|
95
94
|
node.source.gsub('"', '\"')
|
|
96
95
|
end
|
|
97
96
|
end
|
|
98
|
-
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
|
99
97
|
|
|
100
98
|
def handle_special_regexp_chars(begin_node, value)
|
|
101
99
|
parent_node = begin_node.parent
|
|
@@ -155,7 +153,7 @@ module RuboCop
|
|
|
155
153
|
"{#{hash_string}}"
|
|
156
154
|
end
|
|
157
155
|
|
|
158
|
-
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
156
|
+
# rubocop:disable-next Metrics/MethodLength, Metrics/AbcSize
|
|
159
157
|
def autocorrected_value_in_hash(node)
|
|
160
158
|
case node.type
|
|
161
159
|
when :int
|
|
@@ -174,7 +172,6 @@ module RuboCop
|
|
|
174
172
|
node.source.gsub('"', '\"')
|
|
175
173
|
end
|
|
176
174
|
end
|
|
177
|
-
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
178
175
|
|
|
179
176
|
# Does node print its own source when converted to a string?
|
|
180
177
|
def prints_as_self?(node)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# rubocop:disable Lint/RedundantCopDisableDirective
|
|
3
|
+
# rubocop:disable-next Lint/RedundantCopDisableDirective
|
|
4
4
|
module RuboCop
|
|
5
5
|
module Cop
|
|
6
6
|
module Lint
|
|
@@ -55,7 +55,7 @@ module RuboCop
|
|
|
55
55
|
class MissingCopEnableDirective < Base
|
|
56
56
|
include RangeHelp
|
|
57
57
|
|
|
58
|
-
MSG = 'Re-enable %<cop>s %<type>s with
|
|
58
|
+
MSG = 'Re-enable %<cop>s %<type>s with `%<directive>s` after disabling it.'
|
|
59
59
|
MSG_BOUND = 'Re-enable %<cop>s %<type>s within %<max_range>s lines after disabling it.'
|
|
60
60
|
|
|
61
61
|
def on_new_investigation
|
|
@@ -72,7 +72,12 @@ module RuboCop
|
|
|
72
72
|
|
|
73
73
|
def each_missing_enable
|
|
74
74
|
processed_source.disabled_line_ranges.each do |cop, line_ranges|
|
|
75
|
-
line_ranges.each
|
|
75
|
+
line_ranges.each do |line_range|
|
|
76
|
+
# A `disable-next` scope closes itself with its statement.
|
|
77
|
+
next if line_range.respond_to?(:directive) && line_range.directive.disable_next?
|
|
78
|
+
|
|
79
|
+
yield cop, line_range
|
|
80
|
+
end
|
|
76
81
|
end
|
|
77
82
|
end
|
|
78
83
|
|
|
@@ -80,17 +85,22 @@ module RuboCop
|
|
|
80
85
|
# This has to remain a strict inequality to handle
|
|
81
86
|
# the case when max_range is Float::INFINITY
|
|
82
87
|
return true if line_range.max - line_range.min < max_range + 2
|
|
88
|
+
# A name that is not registered as written (e.g. a wrongly-namespaced
|
|
89
|
+
# cop) suppresses nothing; `Lint/RedundantCopDisableDirective` reports
|
|
90
|
+
# it, so demanding its re-enablement would only add noise.
|
|
91
|
+
return true unless Registry.global.names.include?(cop)
|
|
92
|
+
|
|
93
|
+
expected_open_range?(cop, line_range)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def expected_open_range?(cop, line_range)
|
|
83
97
|
# This cop is disabled in the config, it is not expected to be re-enabled
|
|
84
98
|
return true if line_range.min == CommentConfig::CONFIG_DISABLED_LINE_RANGE_MIN
|
|
85
99
|
|
|
86
|
-
cop_class = RuboCop::Cop::Registry.global.find_by_cop_name
|
|
87
|
-
|
|
100
|
+
cop_class = RuboCop::Cop::Registry.global.find_by_cop_name(cop)
|
|
101
|
+
!!(cop_class &&
|
|
88
102
|
!processed_source.registry.enabled?(cop_class, config) &&
|
|
89
|
-
line_range.max == Float::INFINITY
|
|
90
|
-
return true
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
false
|
|
103
|
+
line_range.max == Float::INFINITY)
|
|
94
104
|
end
|
|
95
105
|
|
|
96
106
|
def max_range
|
|
@@ -98,13 +108,17 @@ module RuboCop
|
|
|
98
108
|
end
|
|
99
109
|
|
|
100
110
|
def message(cop, comment, type = 'cop')
|
|
111
|
+
directive = DirectiveComment.new(comment)
|
|
101
112
|
if department_enabled?(cop, comment)
|
|
102
113
|
type = 'department'
|
|
103
114
|
cop = cop.split('/').first
|
|
104
115
|
end
|
|
105
116
|
|
|
106
117
|
if max_range == Float::INFINITY
|
|
107
|
-
|
|
118
|
+
# A range opened by `rubocop:push` is closed by `rubocop:pop`,
|
|
119
|
+
# not by `rubocop:enable`.
|
|
120
|
+
enabling_directive = directive.push? ? '# rubocop:pop' : '# rubocop:enable'
|
|
121
|
+
format(MSG, cop: cop, type: type, directive: enabling_directive)
|
|
108
122
|
else
|
|
109
123
|
format(MSG_BOUND, cop: cop, type: type, max_range: max_range)
|
|
110
124
|
end
|
|
@@ -117,4 +131,3 @@ module RuboCop
|
|
|
117
131
|
end
|
|
118
132
|
end
|
|
119
133
|
end
|
|
120
|
-
# rubocop:enable Lint/RedundantCopDisableDirective
|