rubocop 1.88.2 → 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 +139 -16
- 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 +81 -49
- data/lib/rubocop/config.rb +76 -5
- data/lib/rubocop/config_loader.rb +1 -2
- data/lib/rubocop/config_loader_resolver.rb +1 -2
- data/lib/rubocop/config_validator.rb +39 -3
- data/lib/rubocop/cop/autocorrect_logic.rb +8 -0
- data/lib/rubocop/cop/badge.rb +8 -0
- data/lib/rubocop/cop/base.rb +40 -6
- data/lib/rubocop/cop/bundler.rb +19 -0
- data/lib/rubocop/cop/corrector.rb +4 -4
- data/lib/rubocop/cop/correctors/alignment_corrector.rb +55 -5
- data/lib/rubocop/cop/gemspec.rb +22 -0
- data/lib/rubocop/cop/generator/registration_injector.rb +188 -0
- data/lib/rubocop/cop/generator.rb +4 -0
- 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 +108 -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/first_array_element_indentation.rb +1 -1
- data/lib/rubocop/cop/layout/indentation_width.rb +3 -1
- data/lib/rubocop/cop/layout/leading_comment_space.rb +27 -0
- data/lib/rubocop/cop/layout/line_continuation_spacing.rb +1 -2
- data/lib/rubocop/cop/layout/line_length.rb +84 -12
- data/lib/rubocop/cop/layout/multiline_assignment_layout.rb +1 -2
- data/lib/rubocop/cop/layout/multiline_method_call_indentation.rb +12 -2
- data/lib/rubocop/cop/layout/redundant_line_break.rb +77 -0
- 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 +31 -34
- data/lib/rubocop/cop/layout.rb +114 -0
- data/lib/rubocop/cop/lazy_loader.rb +41 -0
- data/lib/rubocop/cop/lint/ambiguous_assignment.rb +9 -7
- data/lib/rubocop/cop/lint/ambiguous_block_association.rb +71 -0
- 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 +18 -21
- data/lib/rubocop/cop/lint/constant_resolution.rb +25 -0
- data/lib/rubocop/cop/lint/cop_directive_syntax.rb +84 -8
- data/lib/rubocop/cop/lint/deprecated_reference.rb +194 -0
- data/lib/rubocop/cop/lint/duplicate_methods.rb +300 -22
- 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/inherit_exception.rb +53 -7
- data/lib/rubocop/cop/lint/interpolation_check.rb +1 -2
- data/lib/rubocop/cop/lint/literal_as_condition.rb +8 -6
- data/lib/rubocop/cop/lint/literal_in_interpolation.rb +4 -7
- data/lib/rubocop/cop/lint/missing_cop_enable_directive.rb +25 -12
- data/lib/rubocop/cop/lint/missing_super.rb +31 -1
- data/lib/rubocop/cop/lint/name_typo.rb +270 -0
- data/lib/rubocop/cop/lint/out_of_range_regexp_ref.rb +15 -4
- 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 +8 -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 +38 -3
- data/lib/rubocop/cop/lint/unreachable_code.rb +1 -2
- data/lib/rubocop/cop/lint/unused_private_method.rb +234 -0
- 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/useless_ruby2_keywords.rb +2 -0
- data/lib/rubocop/cop/lint/utils/nil_receiver_checker.rb +61 -7
- data/lib/rubocop/cop/lint/void.rb +18 -8
- data/lib/rubocop/cop/lint.rb +178 -0
- data/lib/rubocop/cop/metrics/perceived_complexity.rb +1 -2
- data/lib/rubocop/cop/metrics/utils/code_length_calculator.rb +1 -1
- data/lib/rubocop/cop/metrics.rb +31 -0
- data/lib/rubocop/cop/migration/department_name.rb +1 -6
- data/lib/rubocop/cop/migration.rb +13 -0
- data/lib/rubocop/cop/mixin/check_line_breakable.rb +23 -5
- 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/endless_method_rewriter.rb +13 -5
- data/lib/rubocop/cop/mixin/hash_alignment_styles.rb +23 -6
- 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/project_index_help.rb +140 -0
- data/lib/rubocop/cop/mixin/range_help.rb +1 -2
- data/lib/rubocop/cop/mixin/reparsed_equivalence.rb +171 -0
- data/lib/rubocop/cop/mixin/statement_modifier.rb +46 -4
- data/lib/rubocop/cop/mixin/trailing_comma.rb +2 -4
- data/lib/rubocop/cop/mixin.rb +82 -77
- data/lib/rubocop/cop/naming/accessor_method_name.rb +24 -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/predicate_prefix.rb +33 -5
- data/lib/rubocop/cop/naming/rescued_exceptions_variable_name.rb +1 -2
- data/lib/rubocop/cop/naming/variable_number.rb +3 -1
- data/lib/rubocop/cop/naming.rb +31 -0
- data/lib/rubocop/cop/offense.rb +37 -10
- data/lib/rubocop/cop/registry.rb +180 -46
- data/lib/rubocop/cop/security.rb +19 -0
- 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/array_intersect.rb +24 -5
- 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 +79 -20
- 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 +23 -2
- data/lib/rubocop/cop/style/constant_visibility.rb +1 -2
- data/lib/rubocop/cop/style/copyright.rb +5 -1
- data/lib/rubocop/cop/style/directive_scope.rb +171 -0
- data/lib/rubocop/cop/style/disable_cops_within_source_code_directive.rb +76 -12
- data/lib/rubocop/cop/style/documentation.rb +47 -0
- data/lib/rubocop/cop/style/empty_case_condition.rb +1 -2
- data/lib/rubocop/cop/style/endless_method.rb +0 -10
- 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 +8 -50
- data/lib/rubocop/cop/style/if_unless_modifier_of_if_unless.rb +1 -2
- data/lib/rubocop/cop/style/lambda.rb +4 -0
- data/lib/rubocop/cop/style/map_to_hash.rb +1 -2
- data/lib/rubocop/cop/style/method_call_with_args_parentheses/omit_parentheses.rb +22 -7
- data/lib/rubocop/cop/style/method_call_without_args_parentheses.rb +1 -2
- data/lib/rubocop/cop/style/missing_respond_to_missing.rb +30 -0
- data/lib/rubocop/cop/style/multiline_if_then.rb +7 -1
- data/lib/rubocop/cop/style/multiline_method_signature.rb +1 -2
- data/lib/rubocop/cop/style/multiple_comparison.rb +33 -22
- data/lib/rubocop/cop/style/mutable_constant.rb +5 -0
- data/lib/rubocop/cop/style/negated_if_else_condition.rb +1 -2
- data/lib/rubocop/cop/style/negative_array_index.rb +3 -3
- 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 +12 -2
- data/lib/rubocop/cop/style/redundant_condition.rb +2 -4
- data/lib/rubocop/cop/style/redundant_constant_base.rb +37 -1
- 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_file_extension_in_require.rb +9 -0
- data/lib/rubocop/cop/style/redundant_format.rb +14 -7
- data/lib/rubocop/cop/style/redundant_line_continuation.rb +78 -135
- data/lib/rubocop/cop/style/redundant_parentheses.rb +65 -67
- 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 +11 -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 +21 -5
- data/lib/rubocop/cop/style/stabby_lambda_parentheses.rb +2 -0
- data/lib/rubocop/cop/style/static_class.rb +21 -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 +312 -0
- data/lib/rubocop/cop/team.rb +59 -6
- data/lib/rubocop/cop/util.rb +2 -4
- data/lib/rubocop/cop/variable_force/variable.rb +19 -8
- data/lib/rubocop/cops_documentation_generator.rb +4 -7
- data/lib/rubocop/directive_comment.rb +39 -3
- data/lib/rubocop/feature_loader.rb +0 -6
- 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 +89 -22
- data/lib/rubocop/lsp/runtime.rb +4 -0
- data/lib/rubocop/lsp/server.rb +4 -0
- data/lib/rubocop/lsp/stdin_runner.rb +20 -0
- 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/pending_cops_reporter.rb +1 -6
- data/lib/rubocop/plugin/configuration_integrator.rb +6 -3
- data/lib/rubocop/project_index_loader.rb +16 -0
- data/lib/rubocop/rake_task.rb +1 -2
- data/lib/rubocop/result_cache.rb +6 -4
- data/lib/rubocop/rspec/expect_offense.rb +2 -4
- data/lib/rubocop/rspec/shared_contexts.rb +16 -2
- data/lib/rubocop/runner.rb +71 -43
- data/lib/rubocop/server/cache.rb +11 -7
- 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/target_ruby.rb +1 -1
- data/lib/rubocop/todo_audit.rb +111 -0
- data/lib/rubocop/version.rb +3 -5
- data/lib/rubocop.rb +20 -626
- data/lib/ruby_lsp/rubocop/addon.rb +1 -2
- metadata +28 -5
|
@@ -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
|
|
@@ -21,6 +21,13 @@ module RuboCop
|
|
|
21
21
|
# `AllowedParentClasses` option to specify which classes should be allowed
|
|
22
22
|
# *in addition to* `Object` and `BasicObject`.
|
|
23
23
|
#
|
|
24
|
+
# When `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is installed,
|
|
25
|
+
# the constructor check additionally consults the project-wide index: if the
|
|
26
|
+
# class' entire ancestry is resolvable and no ancestor defines `initialize`,
|
|
27
|
+
# no offense is registered, since `super` would only reach the no-op
|
|
28
|
+
# `Object#initialize`. Classes whose ancestry contains an unresolvable
|
|
29
|
+
# superclass or mixin (e.g. one defined in a gem) are still reported.
|
|
30
|
+
#
|
|
24
31
|
# @example
|
|
25
32
|
# # bad
|
|
26
33
|
# class Employee < Person
|
|
@@ -83,6 +90,8 @@ module RuboCop
|
|
|
83
90
|
# end
|
|
84
91
|
#
|
|
85
92
|
class MissingSuper < Base
|
|
93
|
+
include ProjectIndexHelp
|
|
94
|
+
|
|
86
95
|
CONSTRUCTOR_MSG = 'Call `super` to initialize state of the parent class.'
|
|
87
96
|
CALLBACK_MSG = 'Call `super` to invoke callback defined in the parent class.'
|
|
88
97
|
|
|
@@ -140,7 +149,8 @@ module RuboCop
|
|
|
140
149
|
|
|
141
150
|
!allowed_class?(super_class)
|
|
142
151
|
elsif (class_node = node.each_ancestor(:class).first)
|
|
143
|
-
class_node.parent_class && !allowed_class?(class_node.parent_class)
|
|
152
|
+
class_node.parent_class && !allowed_class?(class_node.parent_class) &&
|
|
153
|
+
!index_verified_stateless_ancestry?(class_node)
|
|
144
154
|
else
|
|
145
155
|
false
|
|
146
156
|
end
|
|
@@ -153,6 +163,26 @@ module RuboCop
|
|
|
153
163
|
def allowed_classes
|
|
154
164
|
@allowed_classes ||= STATELESS_CLASSES + cop_config.fetch('AllowedParentClasses', [])
|
|
155
165
|
end
|
|
166
|
+
|
|
167
|
+
# With the project index, the offense is skipped when the class' whole ancestry
|
|
168
|
+
# is resolvable and none of the inherited ancestors defines `initialize` —
|
|
169
|
+
# `super` would only reach the no-op `Object#initialize`. An unresolvable
|
|
170
|
+
# superclass or mixin anywhere in the chain (e.g. a class from a gem) means
|
|
171
|
+
# the ancestry cannot be verified and the offense is kept.
|
|
172
|
+
def index_verified_stateless_ancestry?(class_node)
|
|
173
|
+
return false unless project_index
|
|
174
|
+
|
|
175
|
+
declaration = resolve_constant_in_index(class_node.identifier)
|
|
176
|
+
return false unless declaration.is_a?(Rubydex::Class)
|
|
177
|
+
|
|
178
|
+
ancestors = declaration.ancestors.to_a
|
|
179
|
+
inherited = ancestors.reject { |ancestor| ancestor.name == declaration.name }
|
|
180
|
+
return false if inherited.any? { |ancestor| ancestor.member('initialize()') }
|
|
181
|
+
|
|
182
|
+
# `extend` affects the singleton class and cannot introduce an
|
|
183
|
+
# inherited `initialize`, so unresolved extends are tolerated.
|
|
184
|
+
fully_resolved_index_ancestry?(declaration, ignore_extend: true)
|
|
185
|
+
end
|
|
156
186
|
end
|
|
157
187
|
end
|
|
158
188
|
end
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'did_you_mean'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
nil
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module RuboCop
|
|
10
|
+
module Cop
|
|
11
|
+
module Lint
|
|
12
|
+
# Checks for probable typos in constant and method names: a name that
|
|
13
|
+
# does not resolve anywhere in the project, used in a namespace the
|
|
14
|
+
# project does define, with a close-named sibling to suggest instead.
|
|
15
|
+
#
|
|
16
|
+
# The check is powered by the project-wide index, so it only runs when
|
|
17
|
+
# `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is installed.
|
|
18
|
+
# Without the index the cop does nothing.
|
|
19
|
+
#
|
|
20
|
+
# Constants are checked only in qualified references (`Foo::Bar`) whose
|
|
21
|
+
# namespace resolves in the index; bare names cannot be distinguished
|
|
22
|
+
# from constants provided by gems or the standard library. Methods are
|
|
23
|
+
# checked only in calls on constant receivers (`Foo.bar`) whose entire
|
|
24
|
+
# indexed ancestry is resolved, so methods gained through gem classes or
|
|
25
|
+
# dynamic definitions never produce offenses. In both cases an offense
|
|
26
|
+
# requires a similarly named alternative to exist — an unknown name
|
|
27
|
+
# alone is not reported, since the index does not see gems or the
|
|
28
|
+
# standard library.
|
|
29
|
+
#
|
|
30
|
+
# For the same reason, a namespace whose root segment names a gem in the
|
|
31
|
+
# bundle (`Flipper` for the `flipper` gem) is left alone: a project that
|
|
32
|
+
# reopens it (`module Flipper; module Adapters; ...`) makes it resolve in
|
|
33
|
+
# the index, while the members the gem itself defines stay invisible and
|
|
34
|
+
# would look like typos. Enabling `AllCops/ProjectIndexIncludesGems`
|
|
35
|
+
# indexes those sources and restores the check.
|
|
36
|
+
#
|
|
37
|
+
# Names that appear as symbols or inside string literals in the same
|
|
38
|
+
# file are never reported, since they usually belong to runtime
|
|
39
|
+
# definitions the index cannot see (`stub_const`, `const_set`,
|
|
40
|
+
# `define_method`, and the like).
|
|
41
|
+
#
|
|
42
|
+
# @example
|
|
43
|
+
# # bad - Services::UserCraetor is not defined, Services::UserCreator is
|
|
44
|
+
# Services::UserCraetor.new
|
|
45
|
+
#
|
|
46
|
+
# # good
|
|
47
|
+
# Services::UserCreator.new
|
|
48
|
+
#
|
|
49
|
+
# # bad - Report.generate_sumary is not defined, Report.generate_summary is
|
|
50
|
+
# Report.generate_sumary
|
|
51
|
+
#
|
|
52
|
+
# # good
|
|
53
|
+
# Report.generate_summary
|
|
54
|
+
#
|
|
55
|
+
# @example CheckConstants: false
|
|
56
|
+
# # good - constant references are not checked
|
|
57
|
+
# Services::UserCraetor.new
|
|
58
|
+
#
|
|
59
|
+
# @example CheckMethods: false
|
|
60
|
+
# # good - method calls are not checked
|
|
61
|
+
# Report.generate_sumary
|
|
62
|
+
#
|
|
63
|
+
# @example AllowedNames: ['generate_sumary']
|
|
64
|
+
# # good - the name is explicitly allowed
|
|
65
|
+
# Report.generate_sumary
|
|
66
|
+
#
|
|
67
|
+
class NameTypo < Base
|
|
68
|
+
include ProjectIndexHelp
|
|
69
|
+
|
|
70
|
+
CONSTANT_MSG = 'Possible typo: `%<name>s` is not defined in `%<namespace>s`. ' \
|
|
71
|
+
'Did you mean `%<suggestion>s`?'
|
|
72
|
+
METHOD_MSG = 'Possible typo: `%<receiver>s` does not respond to `%<name>s`. ' \
|
|
73
|
+
'Did you mean `%<suggestion>s`?'
|
|
74
|
+
|
|
75
|
+
METHOD_MEMBER_REGEXP = /#([a-zA-Z_]\w*[?!]?)\(\)\z/.freeze
|
|
76
|
+
LITERAL_IDENTIFIER_PATTERN = /[a-zA-Z_]\w*[?!]?/.freeze
|
|
77
|
+
|
|
78
|
+
def on_const(node)
|
|
79
|
+
return unless check?('CheckConstants') && checkable_constant?(node)
|
|
80
|
+
|
|
81
|
+
suggestion = constant_typo_suggestion(node)
|
|
82
|
+
return unless suggestion
|
|
83
|
+
|
|
84
|
+
message = format(CONSTANT_MSG, name: node.short_name,
|
|
85
|
+
namespace: node.namespace.const_name,
|
|
86
|
+
suggestion: suggestion)
|
|
87
|
+
add_offense(node.loc.name, message: message)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def on_send(node)
|
|
91
|
+
return unless check?('CheckMethods')
|
|
92
|
+
return unless node.receiver&.const_type?
|
|
93
|
+
return if allowed_name?(node.method_name) || defined_check?(node)
|
|
94
|
+
|
|
95
|
+
suggestion = method_typo_suggestion(node)
|
|
96
|
+
return unless suggestion
|
|
97
|
+
|
|
98
|
+
message = format(METHOD_MSG, receiver: node.receiver.const_name,
|
|
99
|
+
name: node.method_name,
|
|
100
|
+
suggestion: suggestion)
|
|
101
|
+
add_offense(node.loc.selector, message: message)
|
|
102
|
+
end
|
|
103
|
+
alias on_csend on_send
|
|
104
|
+
|
|
105
|
+
def on_new_investigation
|
|
106
|
+
@literal_names = nil
|
|
107
|
+
super
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def check?(key)
|
|
113
|
+
project_index && defined?(DidYouMean::SpellChecker) && cop_config.fetch(key, true)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def checkable_constant?(node)
|
|
117
|
+
node.namespace&.const_type? &&
|
|
118
|
+
!allowed_name?(node.short_name) &&
|
|
119
|
+
!definition_identifier?(node) && !defined_check?(node)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def allowed_name?(name)
|
|
123
|
+
cop_config.fetch('AllowedNames', []).include?(name.to_s)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The last segment of a class, module or constant definition is being
|
|
127
|
+
# defined, not referenced.
|
|
128
|
+
def definition_identifier?(node)
|
|
129
|
+
node.parent&.defined_module
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# `defined?(Foo::Bar)` probes whether a name exists; unknown names
|
|
133
|
+
# there are deliberate.
|
|
134
|
+
def defined_check?(node)
|
|
135
|
+
node.each_ancestor(:defined?).any?
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def constant_typo_suggestion(node)
|
|
139
|
+
namespace = resolve_constant_in_index(node.namespace)
|
|
140
|
+
return nil unless namespace.is_a?(Rubydex::Namespace)
|
|
141
|
+
return nil if namespace.find_member(node.short_name.to_s)
|
|
142
|
+
return nil unless complete_index_members?(namespace)
|
|
143
|
+
return nil if literal_names.include?(node.short_name.to_s)
|
|
144
|
+
|
|
145
|
+
spell_check(node.short_name, constant_member_names(namespace))
|
|
146
|
+
rescue StandardError
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Whether the index can be trusted to list everything the namespace
|
|
151
|
+
# defines. A name missing from an incomplete member list is not
|
|
152
|
+
# evidence of a typo, so both gaps have to be ruled out first:
|
|
153
|
+
# an ancestor that does not resolve may contribute the name, and so
|
|
154
|
+
# may a gem that reopens the namespace.
|
|
155
|
+
def complete_index_members?(declaration)
|
|
156
|
+
fully_resolved_index_ancestry?(declaration) && !gem_owned_namespace?(declaration)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Whether the namespace's root segment names a gem in the bundle,
|
|
160
|
+
# which is then free to define members the index never sees: gem
|
|
161
|
+
# sources are only indexed when `AllCops/ProjectIndexIncludesGems`
|
|
162
|
+
# is enabled. A project that reopens such a namespace to add its own
|
|
163
|
+
# members (`module Flipper; module Adapters; ...`) makes it resolve
|
|
164
|
+
# in the index while most of what it holds stays invisible.
|
|
165
|
+
def gem_owned_namespace?(declaration)
|
|
166
|
+
return false if config.for_all_cops['ProjectIndexIncludesGems']
|
|
167
|
+
|
|
168
|
+
root = declaration.name.to_s.split('::').first.to_s
|
|
169
|
+
bundled_gem_namespaces.include?(root.downcase)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Gem names normalized towards the constant they conventionally
|
|
173
|
+
# provide: `flipper` for `Flipper`, `activerecord` for `ActiveRecord`.
|
|
174
|
+
def bundled_gem_namespaces
|
|
175
|
+
@bundled_gem_namespaces ||= bundled_gem_names.to_set { |name| name.delete('-_').downcase }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# The bundle's gems except those sourced from a local path, whose code
|
|
179
|
+
# lives in the project and is indexed like the rest of it — among them
|
|
180
|
+
# the project's own gem, which its lockfile lists in the `PATH` section.
|
|
181
|
+
def bundled_gem_names
|
|
182
|
+
(config.gem_versions_in_target || {}).keys - (config.path_sourced_gems_in_target || [])
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Writers are indexed under the reader's name, so setter calls are
|
|
186
|
+
# verified and spell-checked through the base name.
|
|
187
|
+
def method_typo_suggestion(node)
|
|
188
|
+
setter = setter_call?(node)
|
|
189
|
+
base = setter ? node.method_name.to_s.delete_suffix('=') : node.method_name.to_s
|
|
190
|
+
|
|
191
|
+
declaration = unknown_method_owner(node, base)
|
|
192
|
+
return nil unless declaration
|
|
193
|
+
return nil if literal_names.include?(base)
|
|
194
|
+
|
|
195
|
+
suggestion = spell_check(base, method_member_names(declaration))
|
|
196
|
+
suggestion && setter ? "#{suggestion}=" : suggestion
|
|
197
|
+
rescue StandardError
|
|
198
|
+
nil
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# The receiver's declaration when it resolves in the index, everything
|
|
202
|
+
# it can inherit or be reopened with is visible there, and the method
|
|
203
|
+
# is not found — nil otherwise.
|
|
204
|
+
def unknown_method_owner(node, base)
|
|
205
|
+
declaration = resolve_constant_in_index(node.receiver)
|
|
206
|
+
return nil unless declaration.is_a?(Rubydex::Namespace)
|
|
207
|
+
return nil if responds_in_index?(declaration, node.method_name.to_s, base)
|
|
208
|
+
return nil unless fully_resolved_index_ancestry?(declaration)
|
|
209
|
+
return nil if gem_owned_namespace?(declaration)
|
|
210
|
+
|
|
211
|
+
declaration
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def setter_call?(node)
|
|
215
|
+
node.assignment_method? && !node.operator_method?
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def responds_in_index?(declaration, name, base)
|
|
219
|
+
# Instance methods of a module are also callable on the module
|
|
220
|
+
# itself when exposed with `module_function`.
|
|
221
|
+
[name, base].uniq.any? do |candidate|
|
|
222
|
+
member_name = "#{candidate}()"
|
|
223
|
+
indexed_singleton_member(declaration, member_name) ||
|
|
224
|
+
declaration.find_member(member_name)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Names mentioned as symbols or inside string literals in the current
|
|
229
|
+
# file belong to runtime definitions (`stub_const`, `const_set`,
|
|
230
|
+
# `define_method`) that the index cannot see.
|
|
231
|
+
def literal_names
|
|
232
|
+
@literal_names ||=
|
|
233
|
+
processed_source.ast.each_descendant(:sym, :str)
|
|
234
|
+
.with_object(Set.new) do |literal, names|
|
|
235
|
+
if literal.sym_type?
|
|
236
|
+
names << literal.value.to_s
|
|
237
|
+
else
|
|
238
|
+
# `scan` raises on binary string literals with invalid byte sequences.
|
|
239
|
+
value = literal.value
|
|
240
|
+
value = value.scrub unless value.valid_encoding?
|
|
241
|
+
value.scan(LITERAL_IDENTIFIER_PATTERN) { |token| names << token }
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def constant_member_names(namespace)
|
|
247
|
+
namespace.members.filter_map do |member|
|
|
248
|
+
name = member.name
|
|
249
|
+
name.split('::').last unless name.include?('#') || name.include?('<')
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def method_member_names(declaration)
|
|
254
|
+
scopes = declaration.ancestors.filter_map { |ancestor| indexed_singleton_of(ancestor) }
|
|
255
|
+
scopes << declaration
|
|
256
|
+
|
|
257
|
+
scopes.flat_map do |scope|
|
|
258
|
+
scope.members.filter_map { |member| member.name[METHOD_MEMBER_REGEXP, 1] }
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def spell_check(name, dictionary)
|
|
263
|
+
return nil if dictionary.empty?
|
|
264
|
+
|
|
265
|
+
DidYouMean::SpellChecker.new(dictionary: dictionary.uniq).correct(name.to_s).first
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
@@ -53,6 +53,8 @@ module RuboCop
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def after_send(node)
|
|
56
|
+
return if backrefs_unaffected?(node)
|
|
57
|
+
|
|
56
58
|
@valid_ref = nil
|
|
57
59
|
|
|
58
60
|
if regexp_first_argument?(node)
|
|
@@ -111,6 +113,19 @@ module RuboCop
|
|
|
111
113
|
end
|
|
112
114
|
end
|
|
113
115
|
|
|
116
|
+
# A call such as `hash[:key]` or `array[0]` cannot involve a regexp match:
|
|
117
|
+
# a non-string literal cannot act as a pattern (whereas `sub`, `gsub`,
|
|
118
|
+
# and `match` treat a string one as a pattern and do set backreferences),
|
|
119
|
+
# and `$~` is frame-local, so a user-defined method cannot change
|
|
120
|
+
# the caller's backreferences either.
|
|
121
|
+
def backrefs_unaffected?(send_node)
|
|
122
|
+
first_argument = send_node.first_argument
|
|
123
|
+
return false unless first_argument&.basic_literal?
|
|
124
|
+
return false if first_argument.str_type?
|
|
125
|
+
|
|
126
|
+
!regexp_receiver?(send_node)
|
|
127
|
+
end
|
|
128
|
+
|
|
114
129
|
def regexp_first_argument?(send_node)
|
|
115
130
|
send_node.first_argument&.regexp_type? \
|
|
116
131
|
&& REGEXP_ARGUMENT_METHODS.include?(send_node.method_name)
|
|
@@ -119,10 +134,6 @@ module RuboCop
|
|
|
119
134
|
def regexp_receiver?(send_node)
|
|
120
135
|
send_node.receiver&.regexp_type?
|
|
121
136
|
end
|
|
122
|
-
|
|
123
|
-
def nth_ref_receiver?(send_node)
|
|
124
|
-
send_node.receiver&.nth_ref_type?
|
|
125
|
-
end
|
|
126
137
|
end
|
|
127
138
|
end
|
|
128
139
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# The Lint/RedundantCopDisableDirective cop needs to be disabled so as
|
|
4
4
|
# to be able to provide a (bad) example of a redundant disable.
|
|
5
|
-
# rubocop:disable Lint/RedundantCopDisableDirective
|
|
5
|
+
# rubocop:disable-next Lint/RedundantCopDisableDirective
|
|
6
6
|
module RuboCop
|
|
7
7
|
module Cop
|
|
8
8
|
module Lint
|
|
@@ -104,17 +104,35 @@ module RuboCop
|
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
def each_redundant_disable(&block)
|
|
107
|
+
each_detached_next_directive(&block)
|
|
108
|
+
|
|
107
109
|
cop_disabled_line_ranges.each do |cop, line_ranges|
|
|
110
|
+
# A pending cop that is not enabled in this run produces no
|
|
111
|
+
# offenses, so its directives cannot be judged - they typically
|
|
112
|
+
# prepare the code for the moment the cop gets enabled.
|
|
113
|
+
next if pending_cop_not_run?(cop)
|
|
114
|
+
|
|
108
115
|
each_already_disabled(cop, line_ranges, &block)
|
|
109
116
|
each_line_range(cop, line_ranges, &block)
|
|
110
117
|
end
|
|
111
118
|
end
|
|
112
119
|
|
|
120
|
+
def pending_cop_not_run?(cop)
|
|
121
|
+
# `Config#for_cop` corrects wrongly-namespaced names, but a name that
|
|
122
|
+
# is not registered as written never suppresses anything, so it is
|
|
123
|
+
# reported as unknown rather than exempted via the corrected cop.
|
|
124
|
+
return false unless all_cop_names.include?(cop)
|
|
125
|
+
|
|
126
|
+
cop_cfg = config.for_cop(cop)
|
|
127
|
+
cop_cfg['Enabled'] == 'pending' &&
|
|
128
|
+
!processed_source.registry.enabled_pending_cop?(cop_cfg, config, cop)
|
|
129
|
+
end
|
|
130
|
+
|
|
113
131
|
def each_line_range(cop, line_ranges)
|
|
114
132
|
line_ranges.each_with_index do |line_range, line_range_index|
|
|
115
133
|
next if should_skip_line_range?(cop, line_range)
|
|
116
134
|
|
|
117
|
-
comment =
|
|
135
|
+
comment = directive_comment(line_range)
|
|
118
136
|
next if skip_directive?(comment)
|
|
119
137
|
|
|
120
138
|
next_range = line_ranges[line_range_index + 1]
|
|
@@ -127,6 +145,24 @@ module RuboCop
|
|
|
127
145
|
ignore_offense?(line_range) || expected_final_disable?(cop, line_range)
|
|
128
146
|
end
|
|
129
147
|
|
|
148
|
+
# A range opened by a `disable-next` directive starts at the statement,
|
|
149
|
+
# not at the directive comment above it, so ask the range itself.
|
|
150
|
+
def directive_comment(line_range)
|
|
151
|
+
if line_range.respond_to?(:directive)
|
|
152
|
+
line_range.directive.comment
|
|
153
|
+
else
|
|
154
|
+
processed_source.comment_at_line(line_range.begin)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# A `disable-next` with no statement attached (or misplaced at the end
|
|
159
|
+
# of a code line) suppresses nothing, so it is redundant by definition.
|
|
160
|
+
def each_detached_next_directive
|
|
161
|
+
processed_source.comment_config.detached_next_directives.each do |directive|
|
|
162
|
+
directive.raw_cop_names.each { |cop| yield directive.comment, cop }
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
130
166
|
def skip_directive?(comment)
|
|
131
167
|
directive = DirectiveComment.new(comment)
|
|
132
168
|
directive.push? || directive.pop?
|
|
@@ -142,7 +178,7 @@ module RuboCop
|
|
|
142
178
|
end
|
|
143
179
|
end
|
|
144
180
|
|
|
145
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
181
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
146
182
|
def each_already_disabled(cop, line_ranges)
|
|
147
183
|
line_ranges.each_cons(2) do |previous_range, range|
|
|
148
184
|
next if ignore_offense?(range)
|
|
@@ -152,7 +188,7 @@ module RuboCop
|
|
|
152
188
|
# whether there are offenses or not.
|
|
153
189
|
next unless followed_ranges?(previous_range, range)
|
|
154
190
|
|
|
155
|
-
comment =
|
|
191
|
+
comment = directive_comment(range)
|
|
156
192
|
|
|
157
193
|
next unless comment
|
|
158
194
|
# Comments disabling all cops don't count since it's reasonable
|
|
@@ -170,7 +206,6 @@ module RuboCop
|
|
|
170
206
|
yield comment, redundant if redundant
|
|
171
207
|
end
|
|
172
208
|
end
|
|
173
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
174
209
|
|
|
175
210
|
def find_redundant_cop(cop, range)
|
|
176
211
|
cop_offenses = offenses_to_check.select { |offense| offense.cop_name == cop }
|
|
@@ -198,7 +233,11 @@ module RuboCop
|
|
|
198
233
|
end
|
|
199
234
|
|
|
200
235
|
def range_with_offense?(range, offenses = offenses_to_check)
|
|
201
|
-
|
|
236
|
+
# An offense is covered when the disabled range overlaps any line of
|
|
237
|
+
# its span, mirroring how offenses are suppressed.
|
|
238
|
+
offenses.none? do |offense|
|
|
239
|
+
offense.line <= range.end && offense.location.last_line >= range.begin
|
|
240
|
+
end
|
|
202
241
|
end
|
|
203
242
|
|
|
204
243
|
def all_disabled?(comment)
|
|
@@ -244,14 +283,28 @@ module RuboCop
|
|
|
244
283
|
location = DirectiveComment.new(comment).range
|
|
245
284
|
cop_names = cops.sort.map { |c| describe(c) }.join(', ')
|
|
246
285
|
|
|
286
|
+
# An unknown cop may just not be loaded in this run (e.g. a custom
|
|
287
|
+
# cop whose configuration failed to load) - removing its directive
|
|
288
|
+
# would destroy something that cannot be restored, so only report.
|
|
289
|
+
# A misplaced EOL `disable-next` should be moved, not deleted.
|
|
290
|
+
if any_unknown_cop?(cops) || misplaced_next_directive?(comment)
|
|
291
|
+
return add_offense(location, message: message(cop_names))
|
|
292
|
+
end
|
|
293
|
+
|
|
247
294
|
add_offense(location, message: message(cop_names)) do |corrector|
|
|
248
|
-
|
|
295
|
+
remove_entire_comment(corrector, comment)
|
|
296
|
+
end
|
|
297
|
+
end
|
|
249
298
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
299
|
+
def remove_entire_comment(corrector, comment)
|
|
300
|
+
# Take the `--` reason with the directive; `leave_free_comment?` keeps anything else.
|
|
301
|
+
directive_range = DirectiveComment.new(comment).range_with_reason
|
|
302
|
+
range = comment_range_with_surrounding_space(directive_range, comment.source_range)
|
|
303
|
+
|
|
304
|
+
if leave_free_comment?(comment, range)
|
|
305
|
+
corrector.replace(range, ' # ')
|
|
306
|
+
else
|
|
307
|
+
corrector.remove(range)
|
|
255
308
|
end
|
|
256
309
|
end
|
|
257
310
|
|
|
@@ -261,14 +314,35 @@ module RuboCop
|
|
|
261
314
|
ranges = cop_ranges.map { |_, r| r }
|
|
262
315
|
|
|
263
316
|
cop_ranges.each do |cop, range|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
317
|
+
add_offense_for_cop_in_list(cop, range, ranges)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def add_offense_for_cop_in_list(cop, range, ranges)
|
|
322
|
+
cop_name = describe(cop)
|
|
323
|
+
return add_offense(range, message: message(cop_name)) if unknown_cop?(cop)
|
|
324
|
+
|
|
325
|
+
add_offense(range, message: message(cop_name)) do |corrector|
|
|
326
|
+
corrector.remove(directive_range_in_list(range, ranges))
|
|
269
327
|
end
|
|
270
328
|
end
|
|
271
329
|
|
|
330
|
+
def any_unknown_cop?(cops)
|
|
331
|
+
cops.any? { |cop| unknown_cop?(cop) }
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def misplaced_next_directive?(comment)
|
|
335
|
+
directive = DirectiveComment.new(comment)
|
|
336
|
+
directive.disable_next? &&
|
|
337
|
+
!processed_source.comment_config.comment_only_line?(directive.line_number)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def unknown_cop?(cop)
|
|
341
|
+
return false if cop == 'all' || department_marker?(cop)
|
|
342
|
+
|
|
343
|
+
!all_cop_names.include?(cop)
|
|
344
|
+
end
|
|
345
|
+
|
|
272
346
|
def leave_free_comment?(comment, range)
|
|
273
347
|
free_comment = comment.text.gsub(range.source.strip, '')
|
|
274
348
|
|
|
@@ -303,7 +377,13 @@ module RuboCop
|
|
|
303
377
|
|
|
304
378
|
SIMILAR_COP_NAMES_CACHE = Hash.new do |hash, cop_name|
|
|
305
379
|
hash[:all_cop_names] = Registry.global.names unless hash.key?(:all_cop_names)
|
|
306
|
-
|
|
380
|
+
# A registered cop with the same base name is a better suggestion
|
|
381
|
+
# than anything spelling similarity can find - the most common
|
|
382
|
+
# mistake is qualifying a cop with the wrong department.
|
|
383
|
+
basename = "/#{cop_name.split('/').last}"
|
|
384
|
+
same_basename = hash[:all_cop_names].find { |name| name.end_with?(basename) }
|
|
385
|
+
hash[cop_name] =
|
|
386
|
+
same_basename || NameSimilarity.find_similar_name(cop_name, hash[:all_cop_names])
|
|
307
387
|
end
|
|
308
388
|
private_constant :SIMILAR_COP_NAMES_CACHE
|
|
309
389
|
|
|
@@ -344,4 +424,3 @@ module RuboCop
|
|
|
344
424
|
end
|
|
345
425
|
end
|
|
346
426
|
end
|
|
347
|
-
# rubocop:enable Lint/RedundantCopDisableDirective
|