rubocop-on-rbs 0.2.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +186 -0
- data/config/default.yml +165 -0
- data/lib/rubocop/cop/rbs/layout/comment_indentation.rb +69 -0
- data/lib/rubocop/cop/rbs/layout/empty_lines_around_overloads.rb +49 -0
- data/lib/rubocop/cop/rbs/layout/end_alignment.rb +55 -0
- data/lib/rubocop/cop/rbs/layout/extra_spacing.rb +55 -0
- data/lib/rubocop/cop/rbs/layout/indentation_width.rb +64 -0
- data/lib/rubocop/cop/rbs/layout/overload_indentation.rb +69 -0
- data/lib/rubocop/cop/rbs/layout/space_around_arrow.rb +56 -0
- data/lib/rubocop/cop/rbs/layout/space_around_braces.rb +77 -0
- data/lib/rubocop/cop/rbs/layout/space_before_colon.rb +43 -0
- data/lib/rubocop/cop/rbs/layout/space_before_overload.rb +46 -0
- data/lib/rubocop/cop/rbs/layout/trailing_whitespace.rb +42 -0
- data/lib/rubocop/cop/rbs/lint/syntax.rb +18 -0
- data/lib/rubocop/cop/rbs/lint/type_params_arity.rb +170 -0
- data/lib/rubocop/cop/rbs/lint/useless_overload_type_params.rb +57 -0
- data/lib/rubocop/cop/rbs/lint/will_syntax_error.rb +205 -0
- data/lib/rubocop/cop/rbs/style/block_return_boolish.rb +35 -0
- data/lib/rubocop/cop/rbs/style/classic_type.rb +73 -0
- data/lib/rubocop/cop/rbs/style/duplicated_type.rb +61 -0
- data/lib/rubocop/cop/rbs/style/initialize_return_type.rb +40 -0
- data/lib/rubocop/cop/rbs/style/merge_untyped.rb +91 -0
- data/lib/rubocop/cop/rbs/style/optional_nil.rb +50 -0
- data/lib/rubocop/cop/rbs/style/true_false.rb +84 -0
- data/lib/rubocop/cop/rbs_cops.rb +28 -0
- data/lib/rubocop/rbs/cop_base.rb +91 -0
- data/lib/rubocop/rbs/inject.rb +20 -0
- data/lib/rubocop/rbs/processed_rbs_source.rb +29 -0
- data/lib/rubocop/rbs/version.rb +7 -0
- data/lib/rubocop/rbs.rb +15 -0
- data/lib/rubocop-on-rbs.rb +13 -0
- metadata +106 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RBS
|
6
|
+
module Layout
|
7
|
+
# @example default
|
8
|
+
# # bad
|
9
|
+
# def foo: () -> void
|
10
|
+
#
|
11
|
+
# # good
|
12
|
+
# def foo: () -> void
|
13
|
+
class ExtraSpacing < RuboCop::RBS::CopBase
|
14
|
+
extend AutoCorrector
|
15
|
+
|
16
|
+
MSG = 'Unnecessary spacing detected.'
|
17
|
+
|
18
|
+
def on_rbs_new_investigation
|
19
|
+
aligned_comments = aligned_locations()
|
20
|
+
tokens = processed_rbs_source.tokens.reject { |t| t.type == :tTRIVIA }
|
21
|
+
tokens.each_cons(2) do |a, b|
|
22
|
+
next unless a&.location
|
23
|
+
next unless b&.location
|
24
|
+
|
25
|
+
if (b.type == :tCOMMENT || b.type == :tLINECOMMENT)
|
26
|
+
next if aligned_comments.include?(b.location.start_line)
|
27
|
+
end
|
28
|
+
next if a.location.end_line != b.location.start_line
|
29
|
+
next if a.location.end_pos + 1 >= b.location.start_pos
|
30
|
+
|
31
|
+
space = range_between(a.location.end_pos, b.location.start_pos - 1)
|
32
|
+
add_offense(space) do |corrector|
|
33
|
+
corrector.remove(space)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def aligned_locations
|
39
|
+
comments = processed_rbs_source.tokens.select(&:comment?)
|
40
|
+
Set.new.tap do |aligned|
|
41
|
+
comments.map(&:location).each_cons(2) do |loc1, loc2|
|
42
|
+
next unless loc1
|
43
|
+
next unless loc2
|
44
|
+
|
45
|
+
if loc1.start_column == loc2.start_column
|
46
|
+
aligned << loc1.start_line << loc2.start_line
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RBS
|
6
|
+
module Layout
|
7
|
+
# @example default
|
8
|
+
# # bad
|
9
|
+
# class Foo
|
10
|
+
# def foo: () -> void
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# class Foo
|
15
|
+
# def foo: () -> void
|
16
|
+
# end
|
17
|
+
class IndentationWidth < RuboCop::RBS::CopBase
|
18
|
+
extend AutoCorrector
|
19
|
+
|
20
|
+
def on_rbs_new_investigation
|
21
|
+
@first_char_columns = processed_source.raw_source.each_line.map do |line|
|
22
|
+
line.index(/[^[:space:]]/) || 0
|
23
|
+
end
|
24
|
+
processed_rbs_source.decls.each do |decl|
|
25
|
+
check_indentation(decl, expect: 0)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_indentation(decl, expect:)
|
30
|
+
if decl.respond_to?(:members)
|
31
|
+
check(decl, expect: expect)
|
32
|
+
decl.members.each do |member|
|
33
|
+
check_indentation(member, expect: expect + 2)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
check(decl, expect: expect)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def check(decl, expect:)
|
41
|
+
line_start_pos = line_start_pos(decl)
|
42
|
+
actual = @first_char_columns[decl.location.start_line - 1]
|
43
|
+
if actual != expect
|
44
|
+
range = range_between(line_start_pos, line_start_pos + actual)
|
45
|
+
message = "Use #{expect} (not #{actual}) spaces for indentation."
|
46
|
+
add_offense(range, message: message) do |corrector|
|
47
|
+
corrector.replace(range, ' ' * expect)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def line_start_pos(decl)
|
53
|
+
rindex = processed_source.raw_source.rindex(/\R/, decl.location.start_pos)
|
54
|
+
if rindex
|
55
|
+
rindex + 1
|
56
|
+
else
|
57
|
+
0
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RBS
|
6
|
+
module Layout
|
7
|
+
# @example default
|
8
|
+
# # bad
|
9
|
+
# def foo: () -> String | () -> (Integer)
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# def foo: () -> String
|
13
|
+
# | () -> (Integer)
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
# def foo: () -> String |
|
17
|
+
# () -> (Integer)
|
18
|
+
#
|
19
|
+
# # good
|
20
|
+
# def foo: () -> String
|
21
|
+
# | () -> Integer
|
22
|
+
class OverloadIndentation < RuboCop::RBS::CopBase
|
23
|
+
extend AutoCorrector
|
24
|
+
|
25
|
+
def on_rbs_def(decl)
|
26
|
+
base_pos = decl.location.start_pos
|
27
|
+
base_col = decl.location.start_column
|
28
|
+
overload_starts = decl.overloads.map { |overload| overload.method_type.location.start_pos }
|
29
|
+
tokens = ::RBS::Parser.lex(decl.location.source).value.reject { |t| t.type == :tTRIVIA }
|
30
|
+
first_colon_column = base_col + tokens.find { |t| t.type == :pCOLON }&.location&.start_column
|
31
|
+
([nil] + tokens).each_cons(3) do |before, bar, after|
|
32
|
+
next unless before
|
33
|
+
next unless bar
|
34
|
+
next unless after
|
35
|
+
next unless bar&.type == :pBAR
|
36
|
+
|
37
|
+
loc = bar&.location
|
38
|
+
next unless loc
|
39
|
+
next unless overload_starts.include?(base_pos + after.location.start_pos)
|
40
|
+
|
41
|
+
if before.location.end_line == bar.location.start_line
|
42
|
+
range = range_between(base_pos + bar.location.start_pos, base_pos + bar.location.end_pos)
|
43
|
+
add_offense(range, message: "Insert newline before `|`") do |corrector|
|
44
|
+
space = range_between(base_pos + before.location.end_pos, base_pos + bar.location.start_pos)
|
45
|
+
corrector.replace(space, "\n#{' ' * first_colon_column}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if bar.location.end_line != after.location.start_line
|
50
|
+
range = range_between(base_pos + bar.location.start_pos, base_pos + bar.location.end_pos)
|
51
|
+
add_offense(range, message: "Remove newline after `|`") do |corrector|
|
52
|
+
space = range_between(base_pos + bar.location.end_pos, base_pos + after.location.start_pos)
|
53
|
+
corrector.replace(space, ' ')
|
54
|
+
end
|
55
|
+
elsif bar.location.start_column != first_colon_column
|
56
|
+
range = range_between(base_pos + bar.location.start_pos, base_pos + bar.location.end_pos)
|
57
|
+
add_offense(range, message: 'Indent the `|` to the first `:`') do |corrector|
|
58
|
+
space = range_between(base_pos + bar.location.start_pos - bar.location.start_column,
|
59
|
+
base_pos + bar.location.start_pos)
|
60
|
+
corrector.replace(space, ' ' * first_colon_column)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RBS
|
6
|
+
module Layout
|
7
|
+
# @example default
|
8
|
+
# # bad
|
9
|
+
# def foo: ()->void
|
10
|
+
#
|
11
|
+
# # bad
|
12
|
+
# def bar: () { ()->void } -> void
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# def foo: () -> void
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# def bar: () { () -> void } -> void
|
19
|
+
class SpaceAroundArrow < RuboCop::RBS::CopBase
|
20
|
+
extend AutoCorrector
|
21
|
+
|
22
|
+
MSG_BEFORE = 'Use one space before `->`.'
|
23
|
+
MSG_AFTER = 'Use one space after `->`.'
|
24
|
+
|
25
|
+
# @sig decl: ::RBS::AST::Members::MethodDefinition
|
26
|
+
def on_rbs_def(decl)
|
27
|
+
base = decl.location.start_pos
|
28
|
+
tokens = ::RBS::Parser.lex(decl.location.source).value.reject { |t| t.type == :tTRIVIA }
|
29
|
+
([nil] + tokens).each_cons(3) do |before, token, after|
|
30
|
+
next unless token&.type == :pARROW
|
31
|
+
|
32
|
+
loc = token&.location
|
33
|
+
next unless loc
|
34
|
+
|
35
|
+
if before && (before.location.end_pos + 1 != loc.start_pos)
|
36
|
+
arrow = location_to_range(loc).adjust(begin_pos: base, end_pos: base)
|
37
|
+
add_offense(arrow, message: MSG_BEFORE) do |corrector|
|
38
|
+
range = range_between(before.location.end_pos, loc.start_pos)
|
39
|
+
corrector.replace(range.adjust(begin_pos: base, end_pos: base), ' ')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
if loc.end_pos + 1 != after.location.start_pos
|
44
|
+
arrow = location_to_range(loc).adjust(begin_pos: base, end_pos: base)
|
45
|
+
add_offense(arrow, message: MSG_AFTER) do |corrector|
|
46
|
+
range = range_between(loc.end_pos, after.location.start_pos)
|
47
|
+
corrector.replace(range.adjust(begin_pos: base, end_pos: base), ' ')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RBS
|
6
|
+
module Layout
|
7
|
+
# @example default
|
8
|
+
# # bad
|
9
|
+
# def bar: (){() -> void}-> void
|
10
|
+
#
|
11
|
+
# # good
|
12
|
+
# def bar: () { () -> void } -> void
|
13
|
+
class SpaceAroundBraces < RuboCop::RBS::CopBase
|
14
|
+
extend AutoCorrector
|
15
|
+
|
16
|
+
# @sig decl: ::RBS::AST::Members::MethodDefinition
|
17
|
+
def on_rbs_def(decl)
|
18
|
+
paren_level = 0
|
19
|
+
tokens = ::RBS::Parser.lex(decl.location.source).value.reject { |t| t.type == :tTRIVIA }
|
20
|
+
([nil] + tokens).each_cons(3) do |before, token, after|
|
21
|
+
case token.type
|
22
|
+
when :pLPAREN # '('
|
23
|
+
paren_level += 1
|
24
|
+
when :pRPAREN # ')'
|
25
|
+
paren_level -= 1
|
26
|
+
when :pLBRACE # '{'
|
27
|
+
next unless before&.type != :pQUESTION # '?'
|
28
|
+
next unless (after.type == :pLPAREN || after.type == :pARROW)
|
29
|
+
next unless paren_level == 0
|
30
|
+
|
31
|
+
check_around_space(decl.location, before, token, after)
|
32
|
+
when :pRBRACE # '}'
|
33
|
+
next unless after.type == :pARROW
|
34
|
+
next unless paren_level == 0
|
35
|
+
|
36
|
+
check_around_space(decl.location, before, token, after)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_around_space(base_loc, before, token, after)
|
42
|
+
return unless before.nil? || before.location.end_line == token.location.start_line
|
43
|
+
return unless token.location.end_line == after.location.start_line
|
44
|
+
|
45
|
+
if before && (before.location.end_pos + 1 != token.location.start_pos)
|
46
|
+
brace = range_between(
|
47
|
+
base_loc.start_pos + token.location.start_pos,
|
48
|
+
base_loc.start_pos + token.location.end_pos,
|
49
|
+
)
|
50
|
+
add_offense(brace, message: "Use one space before `#{token.location.source}`.") do |corrector|
|
51
|
+
range = range_between(
|
52
|
+
base_loc.start_pos + before.location.end_pos,
|
53
|
+
base_loc.start_pos + token.location.start_pos
|
54
|
+
)
|
55
|
+
corrector.replace(range, ' ')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if token.location.end_pos + 1 != after.location.start_pos
|
60
|
+
brace = range_between(
|
61
|
+
base_loc.start_pos + token.location.start_pos,
|
62
|
+
base_loc.start_pos + token.location.end_pos,
|
63
|
+
)
|
64
|
+
add_offense(brace, message: "Use one space after `#{token.location.source}`.") do |corrector|
|
65
|
+
range = range_between(
|
66
|
+
base_loc.start_pos + token.location.end_pos,
|
67
|
+
base_loc.start_pos + after.location.start_pos
|
68
|
+
)
|
69
|
+
corrector.replace(range, ' ')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RBS
|
6
|
+
module Layout
|
7
|
+
# @example default
|
8
|
+
# # bad
|
9
|
+
# def foo : () -> void
|
10
|
+
#
|
11
|
+
# # good
|
12
|
+
# def foo: () -> void
|
13
|
+
class SpaceBeforeColon < RuboCop::RBS::CopBase
|
14
|
+
extend AutoCorrector
|
15
|
+
|
16
|
+
MSG = 'Avoid using spaces before `:`.'
|
17
|
+
|
18
|
+
# @sig decl: ::RBS::AST::Members::MethodDefinition
|
19
|
+
def on_rbs_def(decl)
|
20
|
+
source = processed_source.raw_source
|
21
|
+
loc = decl.location
|
22
|
+
colon_start_pos = source.index(':', loc.start_pos)
|
23
|
+
return unless colon_start_pos
|
24
|
+
|
25
|
+
word_before_colon_pos = source.rindex(/[^\s]/, colon_start_pos - 1)
|
26
|
+
return unless word_before_colon_pos
|
27
|
+
|
28
|
+
if word_before_colon_pos + 1 != colon_start_pos
|
29
|
+
colon = range_between(colon_start_pos, colon_start_pos + 1)
|
30
|
+
add_offense(colon) do |corrector|
|
31
|
+
range = range_between(
|
32
|
+
word_before_colon_pos + 1,
|
33
|
+
colon_start_pos,
|
34
|
+
)
|
35
|
+
corrector.remove(range)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RBS
|
6
|
+
module Layout
|
7
|
+
# @example default
|
8
|
+
# # bad
|
9
|
+
# def foo:() -> void
|
10
|
+
# | () -> void
|
11
|
+
#
|
12
|
+
# # good
|
13
|
+
# def foo: () -> void
|
14
|
+
# | () -> void
|
15
|
+
class SpaceBeforeOverload < RuboCop::RBS::CopBase
|
16
|
+
extend AutoCorrector
|
17
|
+
|
18
|
+
MSG = 'Use one space before overload.'
|
19
|
+
|
20
|
+
# @sig decl: ::RBS::AST::Members::MethodDefinition
|
21
|
+
def on_rbs_def(decl)
|
22
|
+
source = processed_source.raw_source
|
23
|
+
decl.overloads.each_with_index do |overload, i|
|
24
|
+
loc = overload.method_type.location
|
25
|
+
overload_char = i == 0 ? ':' : '|'
|
26
|
+
|
27
|
+
char_start_pos = source.rindex(overload_char, loc.start_pos)
|
28
|
+
next unless char_start_pos
|
29
|
+
|
30
|
+
word_after_char_pos = source.index(/[^\s]/, char_start_pos + 1)
|
31
|
+
next unless word_after_char_pos
|
32
|
+
|
33
|
+
if char_start_pos + 2 != word_after_char_pos
|
34
|
+
char = range_between(char_start_pos, char_start_pos + 1)
|
35
|
+
add_offense(char) do |corrector|
|
36
|
+
range = range_between(char_start_pos + 1, word_after_char_pos)
|
37
|
+
corrector.replace(range, ' ')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RBS
|
6
|
+
module Layout
|
7
|
+
# @example default
|
8
|
+
# # bad
|
9
|
+
# class Foo[:space:]
|
10
|
+
# def foo: () -> void[:space:]
|
11
|
+
# end[:space:]
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# class Foo
|
15
|
+
# def foo: () -> void
|
16
|
+
# end
|
17
|
+
class TrailingWhitespace < RuboCop::RBS::CopBase
|
18
|
+
extend AutoCorrector
|
19
|
+
|
20
|
+
MSG = "Trailing whitespace detected."
|
21
|
+
|
22
|
+
def on_rbs_new_investigation
|
23
|
+
total = 0
|
24
|
+
processed_source.raw_source.each_line do |line|
|
25
|
+
total += line.bytesize
|
26
|
+
chomped = line.chomp
|
27
|
+
next unless chomped.end_with?(' ', "\t")
|
28
|
+
|
29
|
+
range = range_between(
|
30
|
+
total - line.bytesize + chomped.rstrip.bytesize,
|
31
|
+
total - line.bytesize + chomped.bytesize,
|
32
|
+
)
|
33
|
+
add_offense(range) do |corrector|
|
34
|
+
corrector.remove(range)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RBS
|
6
|
+
module Lint
|
7
|
+
# Just only for syntax error
|
8
|
+
class Syntax < RuboCop::RBS::CopBase
|
9
|
+
def on_rbs_parsing_error
|
10
|
+
e = processed_rbs_source.error or raise
|
11
|
+
message = "#{e.error_message}, token=`#{e.location.source}` (#{e.token_type})"
|
12
|
+
add_offense(location_to_range(e.location), message:, severity: :fatal)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module RBS
|
6
|
+
module Lint
|
7
|
+
# This cop checks the arity of type parameters arity.
|
8
|
+
# You can add expect settings in your .rubocop.yml.
|
9
|
+
#
|
10
|
+
# @example Expects settings
|
11
|
+
# RBS/Lint/TypeParamsArity:
|
12
|
+
# Expects:
|
13
|
+
# Your::Class: 1
|
14
|
+
#
|
15
|
+
# @example default
|
16
|
+
# # bad
|
17
|
+
# type a = Array[Integer, String, Symbol]
|
18
|
+
#
|
19
|
+
# # bad
|
20
|
+
# class Foo
|
21
|
+
# include Enumerable
|
22
|
+
# end
|
23
|
+
class TypeParamsArity < RuboCop::RBS::CopBase
|
24
|
+
Types = ::RBS::Types
|
25
|
+
|
26
|
+
def on_rbs_module(decl)
|
27
|
+
check_type_params(decl)
|
28
|
+
decl.self_types.each do |self_type|
|
29
|
+
check(
|
30
|
+
name: self_type.name,
|
31
|
+
args: self_type.args,
|
32
|
+
location: self_type.location
|
33
|
+
)
|
34
|
+
end
|
35
|
+
check_each_mixin(decl)
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_rbs_class(decl)
|
39
|
+
check_type_params(decl)
|
40
|
+
if decl.super_class
|
41
|
+
check(
|
42
|
+
name: decl.super_class.name,
|
43
|
+
args: decl.super_class.args,
|
44
|
+
location: decl.super_class.location
|
45
|
+
)
|
46
|
+
end
|
47
|
+
check_each_mixin(decl)
|
48
|
+
end
|
49
|
+
|
50
|
+
def on_rbs_interface(decl)
|
51
|
+
check_type_params(decl)
|
52
|
+
check_each_mixin(decl)
|
53
|
+
end
|
54
|
+
|
55
|
+
def on_rbs_constant(const)
|
56
|
+
check_type(const.type)
|
57
|
+
end
|
58
|
+
|
59
|
+
def on_rbs_global(global)
|
60
|
+
check_type(global.type)
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_rbs_type_alias(decl)
|
64
|
+
check_type_params(decl)
|
65
|
+
check_type(decl.type)
|
66
|
+
decl.type.each_type do |type|
|
67
|
+
check_type(type)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def on_rbs_def(member)
|
72
|
+
member.overloads.each do |overload|
|
73
|
+
overload.method_type.each_type do |type|
|
74
|
+
check_type(type)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_rbs_attribute(attr)
|
80
|
+
check_type(attr.type)
|
81
|
+
end
|
82
|
+
|
83
|
+
def check_each_mixin(decl)
|
84
|
+
decl.each_mixin do |mixin|
|
85
|
+
check(
|
86
|
+
name: mixin.name,
|
87
|
+
args: mixin.args,
|
88
|
+
location: mixin.location
|
89
|
+
)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def check_type_params(decl)
|
94
|
+
decl.type_params.each do |type_param|
|
95
|
+
if type_param.upper_bound
|
96
|
+
check(
|
97
|
+
name: type_param.upper_bound.name,
|
98
|
+
args: type_param.upper_bound.args,
|
99
|
+
location: type_param.upper_bound.location
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def check_type(type)
|
106
|
+
case type
|
107
|
+
when Types::Record,
|
108
|
+
Types::Tuple,
|
109
|
+
Types::Union,
|
110
|
+
Types::Intersection,
|
111
|
+
Types::Optional,
|
112
|
+
Types::Proc
|
113
|
+
type.each_type.each do |t|
|
114
|
+
check_type(t)
|
115
|
+
end
|
116
|
+
when Types::Interface,
|
117
|
+
Types::Alias,
|
118
|
+
Types::ClassInstance
|
119
|
+
check(
|
120
|
+
name: type.name,
|
121
|
+
args: type.args,
|
122
|
+
location: type.location,
|
123
|
+
)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def check(name:, args:, location:)
|
128
|
+
return unless name.absolute?
|
129
|
+
return unless location
|
130
|
+
|
131
|
+
expect_size = expects[name.to_s]
|
132
|
+
return unless expect_size
|
133
|
+
return unless expect_size != args.size
|
134
|
+
|
135
|
+
message = if args.size == 0
|
136
|
+
"Type `#{name}` is generic but used as a non generic type."
|
137
|
+
else
|
138
|
+
"Type `#{name}` expects #{expect_size} arguments, but #{args.size} arguments are given."
|
139
|
+
end
|
140
|
+
add_offense(
|
141
|
+
location_to_range(location),
|
142
|
+
message: message,
|
143
|
+
severity: :error
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
def expects
|
148
|
+
@expects ||= begin
|
149
|
+
expects = cop_config['Expects']
|
150
|
+
raise "Expects must be a hash" unless expects.is_a?(Hash)
|
151
|
+
|
152
|
+
unless expects.all? { |k, _| k.is_a?(String) }
|
153
|
+
raise "[RBS/Lint/TypeParamsArity] Keys of Expects must be strings"
|
154
|
+
end
|
155
|
+
unless expects.all? { |_, v| v.is_a?(Integer) }
|
156
|
+
raise "[RBS/Lint/TypeParamsArity] Values of Expects must be integers"
|
157
|
+
end
|
158
|
+
|
159
|
+
expects.transform_keys! do |k|
|
160
|
+
k.start_with?('::') ? k : "::#{k}"
|
161
|
+
end
|
162
|
+
|
163
|
+
expects
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|