rubocop-rbs_inline 1.7.0 → 1.8.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/.claude/hooks/self-review.sh +89 -0
- data/.claude/settings.json +8 -0
- data/.rubocop.yml +9 -3
- data/CHANGELOG.md +19 -0
- data/CLAUDE.md +9 -14
- data/README.md +54 -6
- data/config/default.yml +3 -4
- data/lib/rubocop/cop/rbs_inline_cops.rb +15 -7
- data/lib/rubocop/cop/style/rbs_inline/data_class_comment_alignment.rb +2 -9
- data/lib/rubocop/cop/style/rbs_inline/data_define_with_block.rb +3 -9
- data/lib/rubocop/cop/style/rbs_inline/embedded_rbs_spacing.rb +3 -2
- data/lib/rubocop/cop/style/rbs_inline/invalid_comment.rb +2 -2
- data/lib/rubocop/cop/style/rbs_inline/invalid_types.rb +1 -0
- data/lib/rubocop/cop/style/rbs_inline/keyword_separator.rb +2 -2
- data/lib/rubocop/cop/style/rbs_inline/method_comment_spacing.rb +3 -2
- data/lib/rubocop/cop/style/rbs_inline/missing_data_class_annotation.rb +2 -9
- data/lib/rubocop/cop/style/rbs_inline/missing_struct_class_annotation.rb +2 -16
- data/lib/rubocop/cop/style/rbs_inline/missing_type_annotation.rb +3 -16
- data/lib/rubocop/cop/style/rbs_inline/mixin/annotation_keywords.rb +16 -0
- data/lib/rubocop/cop/style/rbs_inline/{ast_utils.rb → mixin/ast_utils.rb} +16 -0
- data/lib/rubocop/cop/style/rbs_inline/{comment_parser.rb → mixin/comment_parser.rb} +8 -0
- data/lib/rubocop/cop/style/rbs_inline/mixin/data_class_matcher.rb +22 -0
- data/lib/rubocop/cop/style/rbs_inline/{data_struct_helper.rb → mixin/data_struct_helper.rb} +14 -5
- data/lib/rubocop/cop/style/rbs_inline/mixin/file_filter.rb +110 -0
- data/lib/rubocop/cop/style/rbs_inline/{source_code_helper.rb → mixin/source_code_helper.rb} +3 -5
- data/lib/rubocop/cop/style/rbs_inline/mixin/struct_class_matcher.rb +32 -0
- data/lib/rubocop/cop/style/rbs_inline/parameters_separator.rb +2 -2
- data/lib/rubocop/cop/style/rbs_inline/redundant_annotation_with_skip.rb +2 -11
- data/lib/rubocop/cop/style/rbs_inline/redundant_instance_variable_annotation.rb +1 -0
- data/lib/rubocop/cop/style/rbs_inline/redundant_type_annotation.rb +6 -15
- data/lib/rubocop/cop/style/rbs_inline/require_rbs_inline_comment.rb +74 -23
- data/lib/rubocop/cop/style/rbs_inline/struct_class_comment_alignment.rb +2 -16
- data/lib/rubocop/cop/style/rbs_inline/struct_new_with_block.rb +3 -9
- data/lib/rubocop/cop/style/rbs_inline/unmatched_annotations.rb +31 -6
- data/lib/rubocop/cop/style/rbs_inline/untyped_instance_variable.rb +16 -6
- data/lib/rubocop/cop/style/rbs_inline/variable_comment_spacing.rb +3 -2
- data/lib/rubocop/rbs_inline/version.rb +1 -1
- data/sig/gems/rubocop/rubocop.rbs +8 -0
- data/sig/rubocop/cop/style/rbs_inline/data_class_comment_alignment.rbs +4 -5
- data/sig/rubocop/cop/style/rbs_inline/data_define_with_block.rbs +4 -5
- data/sig/rubocop/cop/style/rbs_inline/embedded_rbs_spacing.rbs +2 -0
- data/sig/rubocop/cop/style/rbs_inline/invalid_comment.rbs +4 -3
- data/sig/rubocop/cop/style/rbs_inline/invalid_types.rbs +2 -0
- data/sig/rubocop/cop/style/rbs_inline/keyword_separator.rbs +4 -3
- data/sig/rubocop/cop/style/rbs_inline/method_comment_spacing.rbs +2 -0
- data/sig/rubocop/cop/style/rbs_inline/missing_data_class_annotation.rbs +4 -5
- data/sig/rubocop/cop/style/rbs_inline/missing_struct_class_annotation.rbs +4 -10
- data/sig/rubocop/cop/style/rbs_inline/missing_type_annotation.rbs +4 -8
- data/sig/rubocop/cop/style/rbs_inline/mixin/annotation_keywords.rbs +16 -0
- data/sig/rubocop/cop/style/rbs_inline/{ast_utils.rbs → mixin/ast_utils.rbs} +9 -1
- data/sig/rubocop/cop/style/rbs_inline/{class_comment_alignment.rbs → mixin/class_comment_alignment.rbs} +1 -1
- data/sig/rubocop/cop/style/rbs_inline/{comment_parser.rbs → mixin/comment_parser.rbs} +5 -1
- data/sig/rubocop/cop/style/rbs_inline/mixin/data_class_matcher.rbs +18 -0
- data/sig/rubocop/cop/style/rbs_inline/{data_struct_helper.rbs → mixin/data_struct_helper.rbs} +6 -1
- data/sig/rubocop/cop/style/rbs_inline/mixin/file_filter.rbs +63 -0
- data/sig/rubocop/cop/style/rbs_inline/{missing_class_annotation.rbs → mixin/missing_class_annotation.rbs} +1 -1
- data/sig/rubocop/cop/style/rbs_inline/{source_code_helper.rbs → mixin/source_code_helper.rbs} +1 -1
- data/sig/rubocop/cop/style/rbs_inline/mixin/struct_class_matcher.rbs +26 -0
- data/sig/rubocop/cop/style/rbs_inline/parameters_separator.rbs +4 -3
- data/sig/rubocop/cop/style/rbs_inline/redundant_annotation_with_skip.rbs +4 -4
- data/sig/rubocop/cop/style/rbs_inline/redundant_instance_variable_annotation.rbs +2 -0
- data/sig/rubocop/cop/style/rbs_inline/redundant_type_annotation.rbs +4 -4
- data/sig/rubocop/cop/style/rbs_inline/require_rbs_inline_comment.rbs +30 -5
- data/sig/rubocop/cop/style/rbs_inline/struct_class_comment_alignment.rbs +4 -10
- data/sig/rubocop/cop/style/rbs_inline/struct_new_with_block.rbs +4 -5
- data/sig/rubocop/cop/style/rbs_inline/unmatched_annotations.rbs +13 -0
- data/sig/rubocop/cop/style/rbs_inline/untyped_instance_variable.rbs +10 -0
- data/sig/rubocop/cop/style/rbs_inline/variable_comment_spacing.rbs +2 -0
- metadata +24 -17
- data/sig/rubocop/cop/style/rbs_inline/redundant_argument_type.rbs +0 -85
- data/sig/rubocop/cop/style/rbs_inline/redundant_return_type.rbs +0 -109
- /data/lib/rubocop/cop/style/rbs_inline/{class_comment_alignment.rb → mixin/class_comment_alignment.rb} +0 -0
- /data/lib/rubocop/cop/style/rbs_inline/{missing_class_annotation.rb → mixin/missing_class_annotation.rb} +0 -0
|
@@ -13,8 +13,16 @@ module RuboCop
|
|
|
13
13
|
module CommentParser
|
|
14
14
|
attr_reader :parsed_comments #: Array[RBS::Inline::AnnotationParser::ParsingResult]
|
|
15
15
|
|
|
16
|
+
# Fallback for cops that include CommentParser without prepending FileFilter;
|
|
17
|
+
# `FileFilter` overrides this to return the real per-file decision.
|
|
18
|
+
def rbs_inline_file_skipped? #: bool
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
|
|
16
22
|
# Parse comments from the source code
|
|
17
23
|
def parse_comments #: Array[RBS::Inline::AnnotationParser::ParsingResult]
|
|
24
|
+
return @parsed_comments = [] if rbs_inline_file_skipped?
|
|
25
|
+
|
|
18
26
|
parsed_result = Prism.parse(processed_source.buffer.source)
|
|
19
27
|
@parsed_comments = RBS::Inline::AnnotationParser.parse(parsed_result.comments)
|
|
20
28
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
module RbsInline
|
|
7
|
+
# Matcher for `Data.define` calls, shared by the cops that check
|
|
8
|
+
# `Data.define` class definitions.
|
|
9
|
+
module DataClassMatcher
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
13
|
+
def struct_like_class?(node) #: bool
|
|
14
|
+
return false unless node.method_name == :define
|
|
15
|
+
|
|
16
|
+
(r = node.receiver).is_a?(RuboCop::AST::ConstNode) && r.short_name == :Data
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -57,10 +57,13 @@ module RuboCop
|
|
|
57
57
|
comment unless comment&.text&.match?(/\A#:/)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
# The column is determined by the attribute arguments only; non-attribute
|
|
61
|
+
# arguments (e.g. the `Struct.new` name or a `keyword_init:` keyword argument)
|
|
62
|
+
# never carry an annotation and so must not influence the alignment.
|
|
60
63
|
# @rbs node: RuboCop::AST::SendNode
|
|
61
64
|
def annotation_column(node) #: Integer
|
|
62
65
|
last_arg = node.arguments.last
|
|
63
|
-
max_end_col = node.
|
|
66
|
+
max_end_col = attr_arguments(node).map do |arg|
|
|
64
67
|
comma_length = arg.equal?(last_arg) ? 0 : 1
|
|
65
68
|
arg.location.column + source!(arg).length + comma_length
|
|
66
69
|
end.max || 0
|
|
@@ -68,10 +71,12 @@ module RuboCop
|
|
|
68
71
|
max_end_col + 2
|
|
69
72
|
end
|
|
70
73
|
|
|
74
|
+
# Only attribute arguments are considered, for the same reason as
|
|
75
|
+
# {#annotation_column}.
|
|
71
76
|
# @rbs node: RuboCop::AST::SendNode
|
|
72
77
|
def longest_argname(node) #: String
|
|
73
|
-
|
|
74
|
-
args = node.
|
|
78
|
+
last_arg = node.arguments.last
|
|
79
|
+
args = attr_arguments(node).map { _1.equal?(last_arg) ? _1.source.to_s : "#{_1.source}," }
|
|
75
80
|
args.max_by(&:length) || ""
|
|
76
81
|
end
|
|
77
82
|
|
|
@@ -84,8 +89,12 @@ module RuboCop
|
|
|
84
89
|
args_source = node.arguments.each_with_index.map do |arg, i|
|
|
85
90
|
comma = i < last_index ? "," : ""
|
|
86
91
|
prefix = "#{base_indent} #{arg.source}#{comma}"
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
if attr_argument?(arg)
|
|
93
|
+
padding = " " * (longest.length - source!(arg).length - comma.length + 2)
|
|
94
|
+
"#{prefix}#{padding}#: untyped"
|
|
95
|
+
else
|
|
96
|
+
prefix
|
|
97
|
+
end
|
|
89
98
|
end.join("\n")
|
|
90
99
|
|
|
91
100
|
"(\n#{args_source}\n#{base_indent})"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
module RbsInline
|
|
7
|
+
# Filters files a cop reports offenses on, based on `Mode` configuration.
|
|
8
|
+
#
|
|
9
|
+
# When `Mode` is `opt_in`, offenses are only reported for files that contain
|
|
10
|
+
# a `# rbs_inline: enabled` magic comment. When `Mode` is `opt_out`, all files
|
|
11
|
+
# are checked unless they contain `# rbs_inline: disabled`. When `Mode` is not
|
|
12
|
+
# set, all files are checked (legacy behavior).
|
|
13
|
+
#
|
|
14
|
+
# This module is designed to be `prepend`ed to a cop so that it can short-circuit
|
|
15
|
+
# the cop's heavy work (annotation parsing via `parse_comments`) and suppress any
|
|
16
|
+
# residual offense reporting for files that should be skipped.
|
|
17
|
+
#
|
|
18
|
+
# @rbs module-self RuboCop::Cop::Base
|
|
19
|
+
module FileFilter
|
|
20
|
+
# @rbs! type mode = :opt_in | :opt_out
|
|
21
|
+
|
|
22
|
+
MAGIC_COMMENT_ENABLED = /\A# rbs_inline: enabled\R?\z/ #: Regexp
|
|
23
|
+
MAGIC_COMMENT_DISABLED = /\A# rbs_inline: disabled\R?\z/ #: Regexp
|
|
24
|
+
|
|
25
|
+
SUPPORTED_MODES = %i[opt_in opt_out].freeze #: Array[mode]
|
|
26
|
+
|
|
27
|
+
# Tracks Mode values that have already been reported as invalid, so we
|
|
28
|
+
# only emit one warning per typo across the whole rubocop run instead of
|
|
29
|
+
# one per (file × cop).
|
|
30
|
+
# @rbs self.@warned_invalid_modes: Hash[String, bool]
|
|
31
|
+
|
|
32
|
+
@warned_invalid_modes = {} # rubocop:disable Style/RbsInline/UntypedInstanceVariable
|
|
33
|
+
|
|
34
|
+
# @rbs raw: untyped
|
|
35
|
+
def self.warn_invalid_mode(raw) #: void
|
|
36
|
+
key = raw.to_s
|
|
37
|
+
return if @warned_invalid_modes[key]
|
|
38
|
+
|
|
39
|
+
@warned_invalid_modes[key] = true
|
|
40
|
+
Kernel.warn(
|
|
41
|
+
"[rubocop-rbs_inline] Style/RbsInline Mode #{raw.inspect} is not supported. " \
|
|
42
|
+
"Expected one of: #{SUPPORTED_MODES.join(", ")}. Filtering is disabled for this run."
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @rbs @rbs_inline_skip_file: bool
|
|
47
|
+
|
|
48
|
+
def on_new_investigation #: void
|
|
49
|
+
@rbs_inline_skip_file = skip_by_mode?
|
|
50
|
+
super
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @rbs *args: untyped
|
|
54
|
+
# @rbs **kwargs: untyped
|
|
55
|
+
def add_offense(*args, **kwargs, &) #: void
|
|
56
|
+
return if @rbs_inline_skip_file
|
|
57
|
+
|
|
58
|
+
super
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Exposes the FileFilter's per-file skip decision as a proper method so
|
|
62
|
+
# helper modules (e.g. `CommentParser`) can consult it explicitly instead
|
|
63
|
+
# of poking at `@rbs_inline_skip_file` directly.
|
|
64
|
+
def rbs_inline_file_skipped? #: bool
|
|
65
|
+
@rbs_inline_skip_file == true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def skip_by_mode? #: bool
|
|
71
|
+
mode = configured_mode
|
|
72
|
+
return false if mode.nil?
|
|
73
|
+
|
|
74
|
+
# `# rbs_inline: disabled` always opts a file out, regardless of Mode
|
|
75
|
+
# (matches rbs-inline itself: rbs-inline skips disabled files in both
|
|
76
|
+
# opt_in and opt_out modes).
|
|
77
|
+
return true if rbs_inline_disabled?
|
|
78
|
+
|
|
79
|
+
mode == :opt_in && !rbs_inline_enabled?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def configured_mode #: mode?
|
|
83
|
+
raw = cop_config["Mode"]
|
|
84
|
+
return nil if raw.nil?
|
|
85
|
+
|
|
86
|
+
# `raw.to_s.to_sym` handles YAML-native Integer / Boolean without
|
|
87
|
+
# raising NoMethodError on the plain `to_sym`.
|
|
88
|
+
mode = raw.to_s.to_sym
|
|
89
|
+
return mode if SUPPORTED_MODES.include?(mode)
|
|
90
|
+
|
|
91
|
+
FileFilter.warn_invalid_mode(raw)
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Iterate the already-materialized `processed_source.comments` so this
|
|
96
|
+
# matches RequireRbsInlineComment's detection exactly (line endings,
|
|
97
|
+
# indented comments, heredoc/string content are all handled correctly
|
|
98
|
+
# by the parser).
|
|
99
|
+
def rbs_inline_enabled? #: bool
|
|
100
|
+
processed_source.comments.any? { _1.text.match?(MAGIC_COMMENT_ENABLED) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def rbs_inline_disabled? #: bool
|
|
104
|
+
processed_source.comments.any? { _1.text.match?(MAGIC_COMMENT_DISABLED) }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -12,11 +12,9 @@ module RuboCop
|
|
|
12
12
|
# Convert byte offset to character offset
|
|
13
13
|
# @rbs byte_offset: Integer
|
|
14
14
|
def character_offset(byte_offset) #: Integer
|
|
15
|
-
source = processed_source.buffer.source
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
rescue StandardError
|
|
19
|
-
byte_offset
|
|
15
|
+
source = processed_source.buffer.source
|
|
16
|
+
prefix = source.byteslice(0, byte_offset) or raise
|
|
17
|
+
prefix.size
|
|
20
18
|
end
|
|
21
19
|
|
|
22
20
|
# @rbs pos: Integer
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Style
|
|
6
|
+
module RbsInline
|
|
7
|
+
# Matcher for `Struct.new` calls, shared by the cops that check `Struct.new`
|
|
8
|
+
# class definitions.
|
|
9
|
+
#
|
|
10
|
+
# Also overrides {DataStructHelper#attr_argument?} for `Struct.new` semantics,
|
|
11
|
+
# so cops that include both must include this module last.
|
|
12
|
+
module StructClassMatcher
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# @rbs node: RuboCop::AST::SendNode
|
|
16
|
+
def struct_like_class?(node) #: bool
|
|
17
|
+
return false unless node.method_name == :new
|
|
18
|
+
|
|
19
|
+
(r = node.receiver).is_a?(RuboCop::AST::ConstNode) && r.short_name == :Struct
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# `Struct.new` treats a leading string argument as the struct name, so only
|
|
23
|
+
# symbol arguments are attributes.
|
|
24
|
+
# @rbs arg: RuboCop::AST::Node
|
|
25
|
+
def attr_argument?(arg) #: bool
|
|
26
|
+
arg.sym_type?
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -20,13 +20,13 @@ module RuboCop
|
|
|
20
20
|
# # good
|
|
21
21
|
# # @rbs %a{pure}
|
|
22
22
|
class ParametersSeparator < Base
|
|
23
|
+
prepend FileFilter
|
|
23
24
|
extend AutoCorrector
|
|
25
|
+
include AnnotationKeywords
|
|
24
26
|
include RangeHelp
|
|
25
27
|
|
|
26
28
|
MSG = "Use `:` as a separator between parameter name and type."
|
|
27
29
|
|
|
28
|
-
# refs: https://github.com/soutaro/rbs-inline/blob/main/lib/rbs/inline/annotation_parser/tokenizer.rb
|
|
29
|
-
RBS_INLINE_KEYWORDS = %w[inherits override use module-self generic skip module class].freeze #: Array[String]
|
|
30
30
|
RBS_INLINE_REGEXP_KEYWORDS = [/%a{(\w|-)+}/, /%a\((\w|-)+\)/, /%a\[(\w|-)+\]/].freeze #: Array[Regexp]
|
|
31
31
|
|
|
32
32
|
def on_new_investigation #: void
|
|
@@ -58,7 +58,9 @@ module RuboCop
|
|
|
58
58
|
# end
|
|
59
59
|
#
|
|
60
60
|
class RedundantAnnotationWithSkip < Base
|
|
61
|
+
prepend FileFilter
|
|
61
62
|
extend AutoCorrector
|
|
63
|
+
include ASTUtils
|
|
62
64
|
include CommentParser
|
|
63
65
|
include RangeHelp
|
|
64
66
|
include SourceCodeHelper
|
|
@@ -182,17 +184,6 @@ module RuboCop
|
|
|
182
184
|
corrector.remove(removal_range)
|
|
183
185
|
end
|
|
184
186
|
end
|
|
185
|
-
|
|
186
|
-
# Returns the last line of the method parameter list (the closing ) line, or the def line if no parens).
|
|
187
|
-
# @rbs node: RuboCop::AST::DefNode
|
|
188
|
-
def method_parameter_list_end_line(node) #: Integer
|
|
189
|
-
args_node = case node.type
|
|
190
|
-
when :def then node.children[1]
|
|
191
|
-
when :defs then node.children[2]
|
|
192
|
-
else raise
|
|
193
|
-
end
|
|
194
|
-
args_node.location.end&.line || node.location.line
|
|
195
|
-
end
|
|
196
187
|
end
|
|
197
188
|
end
|
|
198
189
|
end
|
|
@@ -80,7 +80,9 @@ module RuboCop
|
|
|
80
80
|
# end
|
|
81
81
|
#
|
|
82
82
|
class RedundantTypeAnnotation < Base
|
|
83
|
+
prepend FileFilter
|
|
83
84
|
extend AutoCorrector
|
|
85
|
+
include ASTUtils
|
|
84
86
|
include CommentParser
|
|
85
87
|
include ConfigurableEnforcedStyle
|
|
86
88
|
include RangeHelp
|
|
@@ -129,17 +131,6 @@ module RuboCop
|
|
|
129
131
|
end
|
|
130
132
|
end
|
|
131
133
|
|
|
132
|
-
# Returns the last line of the method parameter list (the closing ) line, or the def line if no parens).
|
|
133
|
-
# @rbs node: RuboCop::AST::DefNode
|
|
134
|
-
def method_parameter_list_end_line(node) #: Integer
|
|
135
|
-
args_node = case node.type
|
|
136
|
-
when :def then node.children[1]
|
|
137
|
-
when :defs then node.children[2]
|
|
138
|
-
else raise
|
|
139
|
-
end
|
|
140
|
-
args_node.location.end&.line || node.location.line
|
|
141
|
-
end
|
|
142
|
-
|
|
143
134
|
# @rbs def_line: Integer
|
|
144
135
|
# @rbs parameter_list_end_line: Integer
|
|
145
136
|
def check_return_type_redundancy(def_line, parameter_list_end_line) #: void
|
|
@@ -165,7 +156,7 @@ module RuboCop
|
|
|
165
156
|
last = comments.last or return
|
|
166
157
|
range = first.loc.expression.join(last.loc.expression)
|
|
167
158
|
add_offense(range, message: MSG_METHOD_TYPE_SIGNATURE) do |corrector|
|
|
168
|
-
|
|
159
|
+
no_acceptable_style!
|
|
169
160
|
corrector.remove(range_by_whole_lines(range, include_final_newline: true))
|
|
170
161
|
end
|
|
171
162
|
end
|
|
@@ -178,7 +169,7 @@ module RuboCop
|
|
|
178
169
|
def add_offense_for_doc_style_param(annotation) #: void
|
|
179
170
|
range = annotation_range(annotation) or return
|
|
180
171
|
add_offense(range, message: MSG_DOC_STYLE_PARAM) do |corrector|
|
|
181
|
-
|
|
172
|
+
no_acceptable_style!
|
|
182
173
|
corrector.remove(range_by_whole_lines(range, include_final_newline: true))
|
|
183
174
|
end
|
|
184
175
|
end
|
|
@@ -199,7 +190,7 @@ module RuboCop
|
|
|
199
190
|
# @rbs comment: Parser::Source::Comment
|
|
200
191
|
def add_offense_for_trailing_return(comment) #: void
|
|
201
192
|
add_offense(comment, message: MSG_TRAILING_RETURN) do |corrector|
|
|
202
|
-
|
|
193
|
+
no_acceptable_style!
|
|
203
194
|
removal_range = range_with_surrounding_space(range: comment.loc.expression, side: :left, newlines: false)
|
|
204
195
|
corrector.remove(removal_range)
|
|
205
196
|
end
|
|
@@ -209,7 +200,7 @@ module RuboCop
|
|
|
209
200
|
def add_offense_for_doc_style_return(annotation) #: void
|
|
210
201
|
range = annotation_range(annotation) or return
|
|
211
202
|
add_offense(range, message: MSG_DOC_STYLE_RETURN) do |corrector|
|
|
212
|
-
|
|
203
|
+
no_acceptable_style!
|
|
213
204
|
corrector.remove(range_by_whole_lines(range, include_final_newline: true))
|
|
214
205
|
end
|
|
215
206
|
end
|
|
@@ -10,7 +10,7 @@ module RuboCop
|
|
|
10
10
|
# opt-out (processes all files by default). This cop enforces consistency in which
|
|
11
11
|
# mode your codebase uses.
|
|
12
12
|
#
|
|
13
|
-
# @example
|
|
13
|
+
# @example Mode: opt_in
|
|
14
14
|
# # bad
|
|
15
15
|
# # (no rbs_inline comment)
|
|
16
16
|
# class Foo
|
|
@@ -26,7 +26,17 @@ module RuboCop
|
|
|
26
26
|
# class Foo
|
|
27
27
|
# end
|
|
28
28
|
#
|
|
29
|
-
# @example
|
|
29
|
+
# @example Mode: opt_in, AllowMissingComment: true
|
|
30
|
+
# # good - the cop does not enforce the magic comment
|
|
31
|
+
# class Foo
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# # good
|
|
35
|
+
# # rbs_inline: enabled
|
|
36
|
+
# class Foo
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# @example Mode: opt_out
|
|
30
40
|
# # bad
|
|
31
41
|
# # rbs_inline: enabled
|
|
32
42
|
# class Foo
|
|
@@ -49,16 +59,29 @@ module RuboCop
|
|
|
49
59
|
MSG_MISSING = "Missing `# rbs_inline:` magic comment."
|
|
50
60
|
MSG_FORBIDDEN = "Remove `# rbs_inline:` magic comment."
|
|
51
61
|
|
|
62
|
+
# @rbs self.@enforced_style_deprecation_warned: bool
|
|
63
|
+
|
|
64
|
+
@enforced_style_deprecation_warned = false # rubocop:disable Style/RbsInline/UntypedInstanceVariable
|
|
65
|
+
|
|
66
|
+
def self.enforced_style_deprecation_warned? #: bool
|
|
67
|
+
@enforced_style_deprecation_warned == true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.mark_enforced_style_deprecation_warned! #: void
|
|
71
|
+
@enforced_style_deprecation_warned = true
|
|
72
|
+
end
|
|
73
|
+
|
|
52
74
|
def on_new_investigation #: void
|
|
53
75
|
return if processed_source.buffer.source.empty?
|
|
54
76
|
|
|
77
|
+
warn_deprecated_enforced_style
|
|
78
|
+
|
|
55
79
|
magic_comment = find_rbs_inline_magic_comment
|
|
56
80
|
return if disabled?(magic_comment)
|
|
57
81
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
check_never_style(magic_comment)
|
|
82
|
+
case effective_mode
|
|
83
|
+
when :opt_in then check_opt_in(magic_comment)
|
|
84
|
+
when :opt_out then check_opt_out(magic_comment)
|
|
62
85
|
end
|
|
63
86
|
end
|
|
64
87
|
|
|
@@ -76,41 +99,45 @@ module RuboCop
|
|
|
76
99
|
end
|
|
77
100
|
|
|
78
101
|
# @rbs magic_comment: Parser::Source::Comment?
|
|
79
|
-
def
|
|
80
|
-
# disabled is already filtered out by early return
|
|
81
|
-
# magic_comment is either nil or enabled
|
|
102
|
+
def check_opt_in(magic_comment) #: void
|
|
82
103
|
return if magic_comment
|
|
104
|
+
return if allow_missing_comment?
|
|
83
105
|
|
|
84
|
-
# Insert after the first comment block or at the beginning of the file
|
|
85
106
|
insert_position = find_insert_position
|
|
86
107
|
add_offense(first_line_range, message: MSG_MISSING) do |corrector|
|
|
87
108
|
insert_range = Parser::Source::Range.new(processed_source.buffer, insert_position, insert_position)
|
|
88
|
-
corrector.insert_before(insert_range,
|
|
109
|
+
corrector.insert_before(insert_range, insertion_text(insert_position))
|
|
89
110
|
end
|
|
90
111
|
end
|
|
91
112
|
|
|
92
113
|
# @rbs magic_comment: Parser::Source::Comment?
|
|
93
|
-
def
|
|
94
|
-
# disabled is already filtered out by early return
|
|
95
|
-
# magic_comment is either nil or enabled
|
|
114
|
+
def check_opt_out(magic_comment) #: void
|
|
96
115
|
return unless magic_comment
|
|
97
116
|
|
|
98
117
|
add_offense(magic_comment.source_range, message: MSG_FORBIDDEN) do |corrector|
|
|
99
|
-
# Remove the entire line including newline
|
|
100
118
|
range = range_with_surrounding_space(magic_comment.source_range, side: :right, newlines: true)
|
|
101
119
|
corrector.remove(range)
|
|
102
120
|
end
|
|
103
121
|
end
|
|
104
122
|
|
|
105
123
|
def find_insert_position #: Integer
|
|
106
|
-
# Find the end of the first comment block (e.g., magic comments)
|
|
107
|
-
# and insert after it
|
|
108
124
|
first_comment = processed_source.comments.first
|
|
109
|
-
# If the first comment doesn't exist or doesn't start at line 1, insert at the beginning
|
|
110
125
|
return 0 unless first_comment&.source_range&.first_line == 1
|
|
111
126
|
|
|
112
127
|
last_comment_in_block = find_last_comment_in_first_block
|
|
113
|
-
|
|
128
|
+
# `end_pos` points at the newline terminating the comment, so `+ 1` moves past it.
|
|
129
|
+
# A file without a trailing newline has none, so clamp to the end of the buffer.
|
|
130
|
+
(last_comment_in_block.source_range.end_pos + 1).clamp(0, processed_source.buffer.source.length)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The insert position lands at the end of the buffer when the file has no trailing
|
|
134
|
+
# newline. The magic comment then needs its own newline to stay on a separate line.
|
|
135
|
+
# @rbs insert_position: Integer
|
|
136
|
+
def insertion_text(insert_position) #: String
|
|
137
|
+
source = processed_source.buffer.source
|
|
138
|
+
return "# rbs_inline: enabled\n" if insert_position.zero? || source[insert_position - 1] == "\n"
|
|
139
|
+
|
|
140
|
+
"\n# rbs_inline: enabled\n"
|
|
114
141
|
end
|
|
115
142
|
|
|
116
143
|
def find_last_comment_in_first_block #: Parser::Source::Comment
|
|
@@ -127,13 +154,37 @@ module RuboCop
|
|
|
127
154
|
comments[last_idx] || raise
|
|
128
155
|
end
|
|
129
156
|
|
|
130
|
-
def
|
|
131
|
-
cop_config["
|
|
157
|
+
def effective_mode #: FileFilter::mode
|
|
158
|
+
mode = cop_config["Mode"]
|
|
159
|
+
if mode
|
|
160
|
+
# `raw.to_s.to_sym` handles YAML-native Integer / Boolean safely.
|
|
161
|
+
sym = mode.to_s.to_sym
|
|
162
|
+
return sym if FileFilter::SUPPORTED_MODES.include?(sym)
|
|
163
|
+
|
|
164
|
+
FileFilter.warn_invalid_mode(mode)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
cop_config["EnforcedStyle"]&.to_sym == :never ? :opt_out : :opt_in
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def allow_missing_comment? #: bool
|
|
171
|
+
cop_config["AllowMissingComment"] == true
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def warn_deprecated_enforced_style #: void
|
|
175
|
+
return if self.class.enforced_style_deprecation_warned?
|
|
176
|
+
return if cop_config["EnforcedStyle"].nil?
|
|
177
|
+
|
|
178
|
+
self.class.mark_enforced_style_deprecation_warned!
|
|
179
|
+
Kernel.warn(
|
|
180
|
+
"[rubocop-rbs_inline] Style/RbsInline/RequireRbsInlineComment.EnforcedStyle is deprecated. " \
|
|
181
|
+
"Please migrate to `Style/RbsInline: Mode: opt_in` (was `EnforcedStyle: always`) or " \
|
|
182
|
+
"`Style/RbsInline: Mode: opt_out` (was `EnforcedStyle: never`). " \
|
|
183
|
+
"EnforcedStyle will be removed in the next major version."
|
|
184
|
+
)
|
|
132
185
|
end
|
|
133
186
|
|
|
134
187
|
def first_line_range #: Parser::Source::Range
|
|
135
|
-
# Find the first line of actual code (not comment-only lines)
|
|
136
|
-
# If there's no AST (e.g., file with only comments), use the first line
|
|
137
188
|
first_line = processed_source.ast&.source_range&.first_line || 1
|
|
138
189
|
processed_source.buffer.line_range(first_line)
|
|
139
190
|
end
|
|
@@ -24,7 +24,9 @@ module RuboCop
|
|
|
24
24
|
# )
|
|
25
25
|
#
|
|
26
26
|
class StructClassCommentAlignment < Base
|
|
27
|
+
prepend FileFilter
|
|
27
28
|
include ClassCommentAlignment
|
|
29
|
+
include StructClassMatcher
|
|
28
30
|
extend AutoCorrector
|
|
29
31
|
|
|
30
32
|
MSG = "Misaligned inline type annotation for Struct attribute."
|
|
@@ -35,22 +37,6 @@ module RuboCop
|
|
|
35
37
|
|
|
36
38
|
check_comment_alignment(node)
|
|
37
39
|
end
|
|
38
|
-
|
|
39
|
-
private
|
|
40
|
-
|
|
41
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
42
|
-
def struct_like_class?(node) #: bool
|
|
43
|
-
return false unless node.method_name == :new
|
|
44
|
-
|
|
45
|
-
(r = node.receiver).is_a?(RuboCop::AST::ConstNode) && r.short_name == :Struct
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# `Struct.new` treats a leading string argument as the struct name, so only
|
|
49
|
-
# symbol arguments are attributes.
|
|
50
|
-
# @rbs arg: RuboCop::AST::Node
|
|
51
|
-
def attr_argument?(arg) #: bool
|
|
52
|
-
arg.sym_type?
|
|
53
|
-
end
|
|
54
40
|
end
|
|
55
41
|
end
|
|
56
42
|
end
|
|
@@ -25,6 +25,9 @@ module RuboCop
|
|
|
25
25
|
# end
|
|
26
26
|
#
|
|
27
27
|
class StructNewWithBlock < Base
|
|
28
|
+
prepend FileFilter
|
|
29
|
+
include StructClassMatcher
|
|
30
|
+
|
|
28
31
|
MSG = "Do not use `Struct.new` with a block. RBS::Inline does not parse block contents, " \
|
|
29
32
|
"so methods defined in the block will not be recognized. " \
|
|
30
33
|
"Use a separate class definition instead."
|
|
@@ -38,15 +41,6 @@ module RuboCop
|
|
|
38
41
|
|
|
39
42
|
add_offense(node)
|
|
40
43
|
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
# @rbs node: RuboCop::AST::SendNode
|
|
45
|
-
def struct_like_class?(node) #: bool
|
|
46
|
-
return false unless node.method_name == :new
|
|
47
|
-
|
|
48
|
-
(r = node.receiver).is_a?(RuboCop::AST::ConstNode) && r.short_name == :Struct
|
|
49
|
-
end
|
|
50
44
|
end
|
|
51
45
|
end
|
|
52
46
|
end
|
|
@@ -18,14 +18,18 @@ module RuboCop
|
|
|
18
18
|
# def method(arg); end
|
|
19
19
|
#
|
|
20
20
|
class UnmatchedAnnotations < Base
|
|
21
|
+
prepend FileFilter
|
|
21
22
|
include CommentParser
|
|
22
23
|
include RangeHelp
|
|
23
24
|
include SourceCodeHelper
|
|
24
25
|
|
|
25
26
|
MSG = "target parameter not found."
|
|
26
27
|
|
|
28
|
+
attr_reader :processed_comments #: Set[RBS::Inline::AnnotationParser::ParsingResult] -- matched to a def node
|
|
29
|
+
|
|
27
30
|
def on_new_investigation #: void
|
|
28
31
|
super
|
|
32
|
+
@processed_comments = Set.new
|
|
29
33
|
parse_comments
|
|
30
34
|
end
|
|
31
35
|
|
|
@@ -41,6 +45,8 @@ module RuboCop
|
|
|
41
45
|
|
|
42
46
|
def on_investigation_end #: void
|
|
43
47
|
parsed_comments.each do |comment|
|
|
48
|
+
next if processed?(comment)
|
|
49
|
+
|
|
44
50
|
comment.each_annotation do |annotation|
|
|
45
51
|
case annotation
|
|
46
52
|
when RBS::Inline::AST::Annotations::BlockType,
|
|
@@ -57,15 +63,13 @@ module RuboCop
|
|
|
57
63
|
private
|
|
58
64
|
|
|
59
65
|
# @rbs node: RuboCop::AST::DefNode
|
|
60
|
-
def process(node) #: void
|
|
66
|
+
def process(node) #: void
|
|
61
67
|
arguments = arguments_for(node)
|
|
62
68
|
|
|
63
|
-
comment =
|
|
64
|
-
r.comments.map(&:location).map(&:start_line).include?(node.location.line - 1)
|
|
65
|
-
end
|
|
69
|
+
comment = leading_comment_for(node)
|
|
66
70
|
return unless comment
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
mark_processed(comment)
|
|
69
73
|
comment.each_annotation do |annotation|
|
|
70
74
|
case annotation
|
|
71
75
|
when RBS::Inline::AST::Annotations::IvarType
|
|
@@ -76,6 +80,25 @@ module RuboCop
|
|
|
76
80
|
end
|
|
77
81
|
end
|
|
78
82
|
|
|
83
|
+
# @rbs node: RuboCop::AST::DefNode
|
|
84
|
+
def leading_comment_for(node) #: RBS::Inline::AnnotationParser::ParsingResult?
|
|
85
|
+
parsed_comments.find do |comment|
|
|
86
|
+
next if processed?(comment)
|
|
87
|
+
|
|
88
|
+
comment.comments.map(&:location).map(&:start_line).include?(node.location.line - 1)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @rbs comment: RBS::Inline::AnnotationParser::ParsingResult
|
|
93
|
+
def mark_processed(comment) #: void
|
|
94
|
+
processed_comments << comment
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @rbs comment: RBS::Inline::AnnotationParser::ParsingResult
|
|
98
|
+
def processed?(comment) #: bool
|
|
99
|
+
processed_comments.include?(comment)
|
|
100
|
+
end
|
|
101
|
+
|
|
79
102
|
# @rbs node: RuboCop::AST::DefNode
|
|
80
103
|
def arguments_for(node) #: Array[String] # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
81
104
|
args_for(node).children.flat_map do |argument| # rubocop:disable Metrics/BlockLength
|
|
@@ -104,7 +127,9 @@ module RuboCop
|
|
|
104
127
|
when :forward_arg
|
|
105
128
|
["..."]
|
|
106
129
|
else
|
|
107
|
-
|
|
130
|
+
# Unnamed parameters (ex. destructuring arguments; `def foo((a, b))`) are not
|
|
131
|
+
# annotatable because RBS::Inline does not generate types for them.
|
|
132
|
+
[]
|
|
108
133
|
end
|
|
109
134
|
end
|
|
110
135
|
end
|