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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +84 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +186 -0
  6. data/config/default.yml +165 -0
  7. data/lib/rubocop/cop/rbs/layout/comment_indentation.rb +69 -0
  8. data/lib/rubocop/cop/rbs/layout/empty_lines_around_overloads.rb +49 -0
  9. data/lib/rubocop/cop/rbs/layout/end_alignment.rb +55 -0
  10. data/lib/rubocop/cop/rbs/layout/extra_spacing.rb +55 -0
  11. data/lib/rubocop/cop/rbs/layout/indentation_width.rb +64 -0
  12. data/lib/rubocop/cop/rbs/layout/overload_indentation.rb +69 -0
  13. data/lib/rubocop/cop/rbs/layout/space_around_arrow.rb +56 -0
  14. data/lib/rubocop/cop/rbs/layout/space_around_braces.rb +77 -0
  15. data/lib/rubocop/cop/rbs/layout/space_before_colon.rb +43 -0
  16. data/lib/rubocop/cop/rbs/layout/space_before_overload.rb +46 -0
  17. data/lib/rubocop/cop/rbs/layout/trailing_whitespace.rb +42 -0
  18. data/lib/rubocop/cop/rbs/lint/syntax.rb +18 -0
  19. data/lib/rubocop/cop/rbs/lint/type_params_arity.rb +170 -0
  20. data/lib/rubocop/cop/rbs/lint/useless_overload_type_params.rb +57 -0
  21. data/lib/rubocop/cop/rbs/lint/will_syntax_error.rb +205 -0
  22. data/lib/rubocop/cop/rbs/style/block_return_boolish.rb +35 -0
  23. data/lib/rubocop/cop/rbs/style/classic_type.rb +73 -0
  24. data/lib/rubocop/cop/rbs/style/duplicated_type.rb +61 -0
  25. data/lib/rubocop/cop/rbs/style/initialize_return_type.rb +40 -0
  26. data/lib/rubocop/cop/rbs/style/merge_untyped.rb +91 -0
  27. data/lib/rubocop/cop/rbs/style/optional_nil.rb +50 -0
  28. data/lib/rubocop/cop/rbs/style/true_false.rb +84 -0
  29. data/lib/rubocop/cop/rbs_cops.rb +28 -0
  30. data/lib/rubocop/rbs/cop_base.rb +91 -0
  31. data/lib/rubocop/rbs/inject.rb +20 -0
  32. data/lib/rubocop/rbs/processed_rbs_source.rb +29 -0
  33. data/lib/rubocop/rbs/version.rb +7 -0
  34. data/lib/rubocop/rbs.rb +15 -0
  35. data/lib/rubocop-on-rbs.rb +13 -0
  36. 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