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
|
@@ -19,25 +19,27 @@ module RuboCop
|
|
|
19
19
|
# x != y # or x = !y
|
|
20
20
|
#
|
|
21
21
|
class AmbiguousAssignment < Base
|
|
22
|
+
include CheckAssignment
|
|
22
23
|
include RangeHelp
|
|
23
24
|
|
|
24
25
|
MSG = 'Suspicious assignment detected. Did you mean `%<op>s`?'
|
|
25
26
|
|
|
26
|
-
SIMPLE_ASSIGNMENT_TYPES = %i[lvasgn ivasgn cvasgn gvasgn casgn].freeze
|
|
27
|
-
|
|
28
27
|
MISTAKES = { '=-' => '-=', '=+' => '+=', '=*' => '*=', '=!' => '!=' }.freeze
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
alias on_csend on_send
|
|
30
|
+
|
|
31
|
+
private
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
def check_assignment(node, rhs)
|
|
34
|
+
return unless rhs
|
|
35
|
+
return unless (operator = node.loc.operator)
|
|
36
|
+
|
|
37
|
+
range = range_between(operator.end_pos - 1, rhs.source_range.begin_pos + 1)
|
|
34
38
|
source = range.source
|
|
35
39
|
return unless MISTAKES.key?(source)
|
|
36
40
|
|
|
37
41
|
add_offense(range, message: format(MSG, op: MISTAKES[source]))
|
|
38
42
|
end
|
|
39
|
-
|
|
40
|
-
SIMPLE_ASSIGNMENT_TYPES.each { |asgn_type| alias_method :"on_#{asgn_type}", :on_asgn }
|
|
41
43
|
end
|
|
42
44
|
end
|
|
43
45
|
end
|
|
@@ -6,6 +6,11 @@ module RuboCop
|
|
|
6
6
|
# Checks for ambiguous block association with method
|
|
7
7
|
# when param passed without parentheses.
|
|
8
8
|
#
|
|
9
|
+
# This cop also detects `do...end` blocks that are likely intended for
|
|
10
|
+
# an enumerable method in the arguments but actually bind to the outer
|
|
11
|
+
# method call. For example, in `render json: data.map do |x| x end`,
|
|
12
|
+
# Ruby parses the `do...end` block as belonging to `render`, not `map`.
|
|
13
|
+
#
|
|
9
14
|
# This cop can customize allowed methods with `AllowedMethods`.
|
|
10
15
|
# By default, there are no allowed methods.
|
|
11
16
|
#
|
|
@@ -20,6 +25,18 @@ module RuboCop
|
|
|
20
25
|
# # or (different meaning)
|
|
21
26
|
# some_method(a) { |val| puts val }
|
|
22
27
|
#
|
|
28
|
+
# # bad
|
|
29
|
+
# render json: data.map do |item|
|
|
30
|
+
# item.to_h
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# # good
|
|
34
|
+
# render json: data.map { |item| item.to_h }
|
|
35
|
+
#
|
|
36
|
+
# # good
|
|
37
|
+
# mapped = data.map { |item| item.to_h }
|
|
38
|
+
# render json: mapped
|
|
39
|
+
#
|
|
23
40
|
# # good
|
|
24
41
|
# # Operator methods require no disambiguation
|
|
25
42
|
# foo == bar { |b| b.baz }
|
|
@@ -58,6 +75,19 @@ module RuboCop
|
|
|
58
75
|
MSG = 'Parenthesize the param `%<param>s` to make sure that the ' \
|
|
59
76
|
'block will be associated with the `%<method>s` method ' \
|
|
60
77
|
'call.'
|
|
78
|
+
MSG_DO_END_BLOCK = '`%<inner_method>s` is called without a block because the ' \
|
|
79
|
+
'`do` block binds to `%<outer_method>s`. ' \
|
|
80
|
+
'Use braces or extract to a variable.'
|
|
81
|
+
|
|
82
|
+
BLOCK_METHODS = %i[
|
|
83
|
+
map collect flat_map collect_concat
|
|
84
|
+
select filter find_all reject
|
|
85
|
+
find detect
|
|
86
|
+
each each_with_object each_with_index
|
|
87
|
+
reduce inject
|
|
88
|
+
sort_by min_by max_by
|
|
89
|
+
group_by filter_map
|
|
90
|
+
].to_set.freeze
|
|
61
91
|
|
|
62
92
|
def on_send(node)
|
|
63
93
|
return unless node.arguments?
|
|
@@ -74,8 +104,36 @@ module RuboCop
|
|
|
74
104
|
end
|
|
75
105
|
alias on_csend on_send
|
|
76
106
|
|
|
107
|
+
def on_block(node)
|
|
108
|
+
return if node.braces?
|
|
109
|
+
|
|
110
|
+
send_node = node.send_node
|
|
111
|
+
block_method_arg = find_ambiguous_block_method(node)
|
|
112
|
+
return unless block_method_arg
|
|
113
|
+
|
|
114
|
+
add_offense(block_method_arg,
|
|
115
|
+
message: format(MSG_DO_END_BLOCK,
|
|
116
|
+
inner_method: block_method_arg.method_name,
|
|
117
|
+
outer_method: send_node.method_name))
|
|
118
|
+
end
|
|
119
|
+
alias on_numblock on_block
|
|
120
|
+
alias on_itblock on_block
|
|
121
|
+
|
|
77
122
|
private
|
|
78
123
|
|
|
124
|
+
def find_ambiguous_block_method(block_node)
|
|
125
|
+
send_node = block_node.send_node
|
|
126
|
+
return unless send_node.send_type? && send_node.arguments?
|
|
127
|
+
return if send_node.parenthesized?
|
|
128
|
+
|
|
129
|
+
block_method_arg = find_block_method_arg(send_node)
|
|
130
|
+
return unless block_method_arg
|
|
131
|
+
return if allowed_method?(block_method_arg.method_name) ||
|
|
132
|
+
matches_allowed_pattern?(block_method_arg.source)
|
|
133
|
+
|
|
134
|
+
block_method_arg
|
|
135
|
+
end
|
|
136
|
+
|
|
79
137
|
def ambiguous_block_association?(send_node)
|
|
80
138
|
send_node.last_argument.any_block_type? && !send_node.last_argument.send_node.arguments?
|
|
81
139
|
end
|
|
@@ -92,6 +150,19 @@ module RuboCop
|
|
|
92
150
|
format(MSG, param: block_param.source, method: block_param.send_node.source)
|
|
93
151
|
end
|
|
94
152
|
|
|
153
|
+
# Only the call the `do` block could actually have attached to is a candidate:
|
|
154
|
+
# the one whose source ends where the arguments end. This skips calls chained
|
|
155
|
+
# into another call (`foo bar.map.to_a`) and calls buried inside a
|
|
156
|
+
# parenthesized subcall (`foo bar(baz.map)`), which were never block candidates.
|
|
157
|
+
def find_block_method_arg(send_node)
|
|
158
|
+
end_pos = send_node.source_range.end_pos
|
|
159
|
+
|
|
160
|
+
send_node.last_argument.each_node(:call).find do |node|
|
|
161
|
+
node.source_range.end_pos == end_pos &&
|
|
162
|
+
BLOCK_METHODS.include?(node.method_name) && !node.arguments?
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
95
166
|
def wrap_in_parentheses(corrector, node)
|
|
96
167
|
range = node.loc.selector.end.join(node.first_argument.source_range.begin)
|
|
97
168
|
|
|
@@ -83,14 +83,13 @@ module RuboCop
|
|
|
83
83
|
yield range.end if range.end
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
86
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
87
87
|
def acceptable?(node)
|
|
88
88
|
node.begin_type? ||
|
|
89
89
|
node.literal? || rational_literal?(node) ||
|
|
90
90
|
node.variable? || node.const_type? || node.self_type? ||
|
|
91
91
|
(node.call_type? && acceptable_call?(node))
|
|
92
92
|
end
|
|
93
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
94
93
|
|
|
95
94
|
def acceptable_call?(node)
|
|
96
95
|
return true if node.unary_operation?
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Lint
|
|
6
|
+
# Checks for calls that pass the wrong number of positional arguments to a
|
|
7
|
+
# method whose definition is known from the project index.
|
|
8
|
+
#
|
|
9
|
+
# The check is powered by the project-wide index, so it only runs when
|
|
10
|
+
# `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is installed.
|
|
11
|
+
# Without the index the cop does nothing.
|
|
12
|
+
#
|
|
13
|
+
# Only calls on constant receivers (`Foo.bar`) are considered, and only
|
|
14
|
+
# when the receiver resolves in the index, its entire ancestry is resolved
|
|
15
|
+
# (so a method inherited from a gem or the standard library never produces
|
|
16
|
+
# an offense), and the called method resolves to a single, unambiguous
|
|
17
|
+
# signature. Calls that forward arguments (`*`, `**`, `...`) are skipped
|
|
18
|
+
# because their argument count is not statically known. Keyword arguments
|
|
19
|
+
# passed to a method that declares no keyword parameters are counted as the
|
|
20
|
+
# single positional hash they collapse into, matching Ruby's semantics.
|
|
21
|
+
#
|
|
22
|
+
# Constructor calls (`Foo.new`) and keyword-argument validation are out of
|
|
23
|
+
# scope: only positional arity of explicitly defined singleton methods is
|
|
24
|
+
# checked.
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# # Given the project defines:
|
|
28
|
+
# # class Report
|
|
29
|
+
# # def self.generate(source, format); end
|
|
30
|
+
# # end
|
|
31
|
+
#
|
|
32
|
+
# # bad
|
|
33
|
+
# Report.generate(source)
|
|
34
|
+
#
|
|
35
|
+
# # bad
|
|
36
|
+
# Report.generate(source, :pdf, :extra)
|
|
37
|
+
#
|
|
38
|
+
# # good
|
|
39
|
+
# Report.generate(source, :pdf)
|
|
40
|
+
#
|
|
41
|
+
class ArgumentMismatch < Base
|
|
42
|
+
include ProjectIndexHelp
|
|
43
|
+
include IndexedMethodArity
|
|
44
|
+
|
|
45
|
+
MSG = 'Wrong number of arguments to `%<method>s` (given %<given>d, expected %<expected>s).'
|
|
46
|
+
|
|
47
|
+
def on_send(node)
|
|
48
|
+
return unless project_index
|
|
49
|
+
return unless node.receiver&.const_type?
|
|
50
|
+
|
|
51
|
+
shape = resolved_signature_shape(node)
|
|
52
|
+
return unless shape
|
|
53
|
+
|
|
54
|
+
min, max, has_keywords = shape
|
|
55
|
+
given = positional_argument_count(node, has_keywords)
|
|
56
|
+
return if given.nil? || arity_satisfied?(given, min, max)
|
|
57
|
+
|
|
58
|
+
add_offense(node.loc.selector, message: message(node, given, min, max))
|
|
59
|
+
end
|
|
60
|
+
alias on_csend on_send
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def message(node, given, min, max)
|
|
65
|
+
format(MSG, method: node.method_name, given: given, expected: expected_range(min, max))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# `[min, max, has_keywords]` for the called singleton method, or nil when
|
|
69
|
+
# the receiver or its ancestry is not resolved.
|
|
70
|
+
def resolved_signature_shape(node)
|
|
71
|
+
declaration = resolve_constant_in_index(node.receiver)
|
|
72
|
+
return nil unless declaration.is_a?(Rubydex::Namespace)
|
|
73
|
+
return nil unless fully_resolved_index_ancestry?(declaration)
|
|
74
|
+
|
|
75
|
+
singleton_signature_shape(declaration, node.method_name)
|
|
76
|
+
rescue StandardError
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def singleton_signature_shape(declaration, method_name)
|
|
81
|
+
member = indexed_singleton_member(declaration, "#{method_name}()")
|
|
82
|
+
return nil unless member
|
|
83
|
+
|
|
84
|
+
indexed_member_shape(member)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -175,10 +175,19 @@ module RuboCop
|
|
|
175
175
|
end
|
|
176
176
|
|
|
177
177
|
def ancestor_namespaces(node)
|
|
178
|
-
node
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
.
|
|
178
|
+
ancestors = node.each_ancestor(:class, :module).reverse_each
|
|
179
|
+
|
|
180
|
+
ancestors.with_object([]) do |ancestor, namespaces|
|
|
181
|
+
append_namespaces(namespaces, ancestor.identifier)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Compact definitions (e.g. `module A::B`) contribute every path segment,
|
|
186
|
+
# and an absolute one (e.g. `module ::A::B`) discards the enclosing namespaces.
|
|
187
|
+
def append_namespaces(namespaces, identifier)
|
|
188
|
+
namespaces.clear if identifier.absolute?
|
|
189
|
+
namespaces.concat(identifier_namespaces(identifier))
|
|
190
|
+
namespaces << identifier.short_name
|
|
182
191
|
end
|
|
183
192
|
|
|
184
193
|
def unconditional_definition?(node)
|
|
@@ -188,14 +197,11 @@ module RuboCop
|
|
|
188
197
|
end
|
|
189
198
|
|
|
190
199
|
def definition_name(node)
|
|
191
|
-
|
|
200
|
+
namespaces = ancestor_namespaces(node)
|
|
201
|
+
append_namespaces(namespaces, node.identifier)
|
|
202
|
+
constant = namespaces.pop
|
|
192
203
|
|
|
193
|
-
|
|
194
|
-
fully_qualified_name_for([], identifier.short_name)
|
|
195
|
-
else
|
|
196
|
-
namespaces = ancestor_namespaces(node) + identifier_namespaces(identifier)
|
|
197
|
-
fully_qualified_name_for(namespaces, identifier.short_name)
|
|
198
|
-
end
|
|
204
|
+
fully_qualified_name_for(namespaces, constant)
|
|
199
205
|
end
|
|
200
206
|
|
|
201
207
|
def identifier_namespaces(identifier)
|
|
@@ -209,21 +215,12 @@ module RuboCop
|
|
|
209
215
|
def report_cross_file_collision(node, fully_qualified_name, display_name)
|
|
210
216
|
return unless project_index
|
|
211
217
|
return unless (declaration = project_index[fully_qualified_name.delete_prefix('::')])
|
|
212
|
-
return unless (prior = prior_definition_in_other_file(declaration))
|
|
218
|
+
return unless (prior = prior_definition_in_other_file(declaration.definitions))
|
|
213
219
|
|
|
214
220
|
msg = format(CROSS_FILE_MSG, constant: display_name, path: prior.location.to_file_path)
|
|
215
221
|
|
|
216
222
|
add_offense(node, message: msg)
|
|
217
223
|
end
|
|
218
|
-
|
|
219
|
-
def prior_definition_in_other_file(declaration)
|
|
220
|
-
current = processed_source.file_path
|
|
221
|
-
|
|
222
|
-
declaration.definitions.find do |definition|
|
|
223
|
-
other = definition.location.to_file_path
|
|
224
|
-
!File.identical?(other, current)
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
224
|
end
|
|
228
225
|
end
|
|
229
226
|
end
|
|
@@ -20,6 +20,12 @@ module RuboCop
|
|
|
20
20
|
# to prevent conflicting rules. This is because it respects user configurations
|
|
21
21
|
# that want to enable this cop which is disabled by default.
|
|
22
22
|
#
|
|
23
|
+
# When `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is
|
|
24
|
+
# installed, only genuinely ambiguous constants are reported: those
|
|
25
|
+
# that resolve to a different declaration through the surrounding
|
|
26
|
+
# nesting than they would fully qualified. This makes the cop practical
|
|
27
|
+
# to enable without `Only`/`Ignore` lists.
|
|
28
|
+
#
|
|
23
29
|
# @example
|
|
24
30
|
# # By default checks every constant
|
|
25
31
|
#
|
|
@@ -60,6 +66,8 @@ module RuboCop
|
|
|
60
66
|
# Login
|
|
61
67
|
#
|
|
62
68
|
class ConstantResolution < Base
|
|
69
|
+
include ProjectIndexHelp
|
|
70
|
+
|
|
63
71
|
MSG = 'Fully qualify this constant to avoid possibly ambiguous resolution.'
|
|
64
72
|
|
|
65
73
|
# @!method unqualified_const?(node)
|
|
@@ -69,12 +77,29 @@ module RuboCop
|
|
|
69
77
|
|
|
70
78
|
def on_const(node)
|
|
71
79
|
return if !unqualified_const?(node) || node.parent&.defined_module || node.loc.nil?
|
|
80
|
+
return if project_index && !ambiguous_resolution?(node)
|
|
72
81
|
|
|
73
82
|
add_offense(node)
|
|
74
83
|
end
|
|
75
84
|
|
|
76
85
|
private
|
|
77
86
|
|
|
87
|
+
# When `AllCops/UseProjectIndex` is enabled, only genuinely ambiguous
|
|
88
|
+
# references are reported: the constant resolves to a different
|
|
89
|
+
# declaration through the lexical nesting than it would fully
|
|
90
|
+
# qualified. Names that resolve identically either way, or that the
|
|
91
|
+
# index cannot resolve at all (e.g. constants from gems), are not
|
|
92
|
+
# reported.
|
|
93
|
+
def ambiguous_resolution?(node)
|
|
94
|
+
name = node.short_name.to_s
|
|
95
|
+
relative = project_index.resolve_constant(name, lexical_nesting_of(node))
|
|
96
|
+
absolute = project_index.resolve_constant(name, [])
|
|
97
|
+
|
|
98
|
+
!relative.nil? && !absolute.nil? && relative.name != absolute.name
|
|
99
|
+
rescue StandardError
|
|
100
|
+
false
|
|
101
|
+
end
|
|
102
|
+
|
|
78
103
|
def const_name?(name)
|
|
79
104
|
name = name.to_s
|
|
80
105
|
(allowed_names.empty? || allowed_names.include?(name)) && !ignored_names.include?(name)
|
|
@@ -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
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Lint
|
|
6
|
+
# Checks for calls to methods and references to constants that are documented
|
|
7
|
+
# as deprecated with a YARD `@deprecated` tag.
|
|
8
|
+
#
|
|
9
|
+
# The check is powered by the project-wide index, so it only runs when
|
|
10
|
+
# `AllCops/UseProjectIndex` is enabled and the `rubydex` gem is installed.
|
|
11
|
+
# Without the index the cop does nothing.
|
|
12
|
+
#
|
|
13
|
+
# Only references that can be resolved without type inference are checked:
|
|
14
|
+
# constants, method calls without an explicit receiver (or with `self`),
|
|
15
|
+
# which are looked up in the enclosing class or module and its ancestry,
|
|
16
|
+
# and calls whose receiver is a constant, which are looked up in that
|
|
17
|
+
# namespace's singleton class. Calls on arbitrary objects are not checked.
|
|
18
|
+
#
|
|
19
|
+
# References made from a definition that is itself deprecated are allowed,
|
|
20
|
+
# so deprecated implementations can keep calling each other.
|
|
21
|
+
#
|
|
22
|
+
# @example
|
|
23
|
+
# # Given a deprecated method and constant:
|
|
24
|
+
# #
|
|
25
|
+
# # class Api
|
|
26
|
+
# # # @deprecated Use `#new_method` instead.
|
|
27
|
+
# # def old_method
|
|
28
|
+
# # end
|
|
29
|
+
# #
|
|
30
|
+
# # # @deprecated
|
|
31
|
+
# # OLD_TIMEOUT = 10
|
|
32
|
+
# # end
|
|
33
|
+
#
|
|
34
|
+
# # bad
|
|
35
|
+
# class Client < Api
|
|
36
|
+
# def call
|
|
37
|
+
# old_method
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# def timeout
|
|
41
|
+
# OLD_TIMEOUT
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# # good
|
|
46
|
+
# class Client < Api
|
|
47
|
+
# def call
|
|
48
|
+
# new_method
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# def timeout
|
|
52
|
+
# NEW_TIMEOUT
|
|
53
|
+
# end
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
class DeprecatedReference < Base
|
|
57
|
+
include ProjectIndexHelp
|
|
58
|
+
|
|
59
|
+
METHOD_MSG = 'Method `%<name>s` is deprecated%<detail>s'
|
|
60
|
+
CONSTANT_MSG = 'Constant `%<name>s` is deprecated%<detail>s'
|
|
61
|
+
|
|
62
|
+
DEPRECATED_TAG = '@deprecated'
|
|
63
|
+
DEPRECATION_DETAIL = /#{DEPRECATED_TAG}\s+(.+)/.freeze
|
|
64
|
+
|
|
65
|
+
def on_new_investigation
|
|
66
|
+
@namespace_cache = {}
|
|
67
|
+
super
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def on_send(node)
|
|
71
|
+
return unless project_index
|
|
72
|
+
|
|
73
|
+
declaration = method_declaration_for(node)
|
|
74
|
+
return unless declaration && deprecated?(declaration)
|
|
75
|
+
return if within_deprecated_definition?(node)
|
|
76
|
+
|
|
77
|
+
message = format(METHOD_MSG, name: node.method_name, detail: detail_for(declaration))
|
|
78
|
+
add_offense(node.loc.selector, message: message)
|
|
79
|
+
end
|
|
80
|
+
alias on_csend on_send
|
|
81
|
+
|
|
82
|
+
def on_const(node)
|
|
83
|
+
return unless project_index
|
|
84
|
+
return if node.parent&.defined_module
|
|
85
|
+
|
|
86
|
+
declaration = resolve_constant_in_index(node)
|
|
87
|
+
return unless declaration && deprecated?(declaration)
|
|
88
|
+
return if within_deprecated_definition?(node)
|
|
89
|
+
|
|
90
|
+
message = format(CONSTANT_MSG, name: node.const_name, detail: detail_for(declaration))
|
|
91
|
+
add_offense(node, message: message)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def method_declaration_for(node)
|
|
97
|
+
receiver = node.receiver
|
|
98
|
+
|
|
99
|
+
if receiver.nil? || receiver.self_type?
|
|
100
|
+
implicit_receiver_declaration(node)
|
|
101
|
+
elsif receiver.const_type?
|
|
102
|
+
const_receiver_declaration(node, receiver)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def implicit_receiver_declaration(node)
|
|
107
|
+
namespace = enclosing_namespace(node)
|
|
108
|
+
return nil unless namespace.is_a?(Rubydex::Namespace)
|
|
109
|
+
|
|
110
|
+
if singleton_context?(node)
|
|
111
|
+
indexed_singleton_member(namespace, "#{node.method_name}()")
|
|
112
|
+
else
|
|
113
|
+
namespace.find_member("#{node.method_name}()")
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def const_receiver_declaration(node, receiver)
|
|
118
|
+
namespace = resolve_constant_in_index(receiver)
|
|
119
|
+
return nil unless namespace.is_a?(Rubydex::Namespace)
|
|
120
|
+
|
|
121
|
+
indexed_singleton_member(namespace, "#{node.method_name}()")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def enclosing_namespace(node)
|
|
125
|
+
namespace_node = node.each_ancestor(:class, :module).first
|
|
126
|
+
return project_index['Object'] unless namespace_node
|
|
127
|
+
|
|
128
|
+
@namespace_cache.fetch(namespace_node) do
|
|
129
|
+
@namespace_cache[namespace_node] = resolve_constant_in_index(namespace_node.identifier)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Whether an implicit-receiver call runs with the class or module itself
|
|
134
|
+
# as `self` (directly in a namespace body, in a singleton method, or
|
|
135
|
+
# inside `class << self`) rather than inside an instance method.
|
|
136
|
+
def singleton_context?(node)
|
|
137
|
+
instance_def_seen = false
|
|
138
|
+
|
|
139
|
+
node.each_ancestor do |ancestor|
|
|
140
|
+
case ancestor.type
|
|
141
|
+
when :defs, :sclass
|
|
142
|
+
return true
|
|
143
|
+
when :def
|
|
144
|
+
instance_def_seen = true
|
|
145
|
+
when :class, :module
|
|
146
|
+
return !instance_def_seen
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
false
|
|
151
|
+
end
|
|
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.
|
|
156
|
+
def deprecated?(declaration)
|
|
157
|
+
any = false
|
|
158
|
+
|
|
159
|
+
declaration.definitions.each do |definition|
|
|
160
|
+
return false unless definition.deprecated?
|
|
161
|
+
|
|
162
|
+
any = true
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
any
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# References from a definition that is itself documented as deprecated
|
|
169
|
+
# are allowed.
|
|
170
|
+
def within_deprecated_definition?(node)
|
|
171
|
+
node.each_ancestor(:any_def, :class, :module).any? do |ancestor|
|
|
172
|
+
comments = processed_source.ast_with_comments[ancestor]
|
|
173
|
+
comments.any? { |comment| comment.text.include?(DEPRECATED_TAG) }
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def detail_for(declaration)
|
|
178
|
+
text = deprecation_text(declaration)
|
|
179
|
+
return '.' if text.nil? || text.empty?
|
|
180
|
+
|
|
181
|
+
text.end_with?('.') ? ": #{text}" : ": #{text}."
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def deprecation_text(declaration)
|
|
185
|
+
tag_comment = declaration.definitions.to_a.filter_map do |definition|
|
|
186
|
+
definition.comments.to_a.map(&:string).find { |string| string.include?(DEPRECATED_TAG) }
|
|
187
|
+
end.first
|
|
188
|
+
|
|
189
|
+
tag_comment && tag_comment[DEPRECATION_DETAIL, 1]&.strip
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|